Skip to content

Commit 3752e3d

Browse files
committed
Detect more overflows in timestamp[tz]_pl_interval.
In commit 25cd2d6 I (tgl) opined that "The additions of the months and microseconds fields could also overflow, of course. However, I believe we need no additional checks there; the existing range checks should catch such cases". This is demonstrably wrong however for the microseconds field, and given that discovery it seems prudent to be paranoid about the months addition as well. Report and patch by Joseph Koshakow. As before, back-patch to all supported branches. (However, the test case doesn't work before v15 because we didn't allow wider-than-int32 numbers in interval literals. A variant test could probably be built that fits within that restriction, but it didn't seem worth the trouble.) Discussion: https://postgr.es/m/CAAvxfHf77sRHKoEzUw9_cMYSpbpNS2C+J_+8Dq4+0oi8iKopeA@mail.gmail.com
1 parent 52f4461 commit 3752e3d

File tree

3 files changed

+22
-4
lines changed

3 files changed

+22
-4
lines changed

src/backend/utils/adt/timestamp.c

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2932,7 +2932,10 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
29322932
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
29332933
errmsg("timestamp out of range")));
29342934

2935-
tm->tm_mon += span->month;
2935+
if (pg_add_s32_overflow(tm->tm_mon, span->month, &tm->tm_mon))
2936+
ereport(ERROR,
2937+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2938+
errmsg("timestamp out of range")));
29362939
if (tm->tm_mon > MONTHS_PER_YEAR)
29372940
{
29382941
tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR;
@@ -2984,7 +2987,10 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
29842987
errmsg("timestamp out of range")));
29852988
}
29862989

2987-
timestamp += span->time;
2990+
if (pg_add_s64_overflow(timestamp, span->time, &timestamp))
2991+
ereport(ERROR,
2992+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2993+
errmsg("timestamp out of range")));
29882994

29892995
if (!IS_VALID_TIMESTAMP(timestamp))
29902996
ereport(ERROR,
@@ -3052,7 +3058,10 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
30523058
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
30533059
errmsg("timestamp out of range")));
30543060

3055-
tm->tm_mon += span->month;
3061+
if (pg_add_s32_overflow(tm->tm_mon, span->month, &tm->tm_mon))
3062+
ereport(ERROR,
3063+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3064+
errmsg("timestamp out of range")));
30563065
if (tm->tm_mon > MONTHS_PER_YEAR)
30573066
{
30583067
tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR;
@@ -3111,7 +3120,10 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
31113120
errmsg("timestamp out of range")));
31123121
}
31133122

3114-
timestamp += span->time;
3123+
if (pg_add_s64_overflow(timestamp, span->time, &timestamp))
3124+
ereport(ERROR,
3125+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3126+
errmsg("timestamp out of range")));
31153127

31163128
if (!IS_VALID_TIMESTAMP(timestamp))
31173129
ereport(ERROR,

src/test/regress/expected/horology.out

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,8 @@ SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '109203489 days'
484484

485485
SELECT timestamp without time zone '2000-01-01' - interval '2483590 days' AS "out of range";
486486
ERROR: timestamp out of range
487+
SELECT timestamp without time zone '294276-12-31 23:59:59' + interval '9223372036854775807 microseconds' AS "out of range";
488+
ERROR: timestamp out of range
487489
SELECT timestamp without time zone '12/31/294276' - timestamp without time zone '12/23/1999' AS "106751991 Days";
488490
106751991 Days
489491
------------------
@@ -746,6 +748,8 @@ SELECT timestamp with time zone '1999-12-01' + interval '1 month - 1 second' AS
746748

747749
SELECT timestamp with time zone '2000-01-01' - interval '2483590 days' AS "out of range";
748750
ERROR: timestamp out of range
751+
SELECT timestamp with time zone '294276-12-31 23:59:59 UTC' + interval '9223372036854775807 microseconds' AS "out of range";
752+
ERROR: timestamp out of range
749753
SELECT (timestamp with time zone 'today' = (timestamp with time zone 'yesterday' + interval '1 day')) as "True";
750754
True
751755
------

src/test/regress/sql/horology.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '106000000 days'
121121
SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '107000000 days' AS "Jan 20, 288244";
122122
SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '109203489 days' AS "Dec 31, 294276";
123123
SELECT timestamp without time zone '2000-01-01' - interval '2483590 days' AS "out of range";
124+
SELECT timestamp without time zone '294276-12-31 23:59:59' + interval '9223372036854775807 microseconds' AS "out of range";
124125
SELECT timestamp without time zone '12/31/294276' - timestamp without time zone '12/23/1999' AS "106751991 Days";
125126

126127
-- Shorthand values
@@ -153,6 +154,7 @@ SELECT timestamp with time zone '1999-03-01' - interval '1 second' AS "Feb 28";
153154
SELECT timestamp with time zone '2000-03-01' - interval '1 second' AS "Feb 29";
154155
SELECT timestamp with time zone '1999-12-01' + interval '1 month - 1 second' AS "Dec 31";
155156
SELECT timestamp with time zone '2000-01-01' - interval '2483590 days' AS "out of range";
157+
SELECT timestamp with time zone '294276-12-31 23:59:59 UTC' + interval '9223372036854775807 microseconds' AS "out of range";
156158

157159
SELECT (timestamp with time zone 'today' = (timestamp with time zone 'yesterday' + interval '1 day')) as "True";
158160
SELECT (timestamp with time zone 'today' = (timestamp with time zone 'tomorrow' - interval '1 day')) as "True";

0 commit comments

Comments
 (0)