Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 25cd2d6

Browse files
committed
Detect Julian-date overflow in timestamp[tz]_pl_interval.
We perform addition of the days field of an interval via arithmetic on the Julian-date representation of the timestamp's date. This step is subject to int32 overflow, and we also should not let the Julian date become very negative, for fear of weird results from j2date. (In the timestamptz case, allow a Julian date of -1 to pass, since it might convert back to zero after timezone rotation.) 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. The difficulty here is that j2date's magic modular arithmetic could produce something that looks like it's in-range. Per bug #18313 from Christian Maurer. This has been wrong for a long time, so back-patch to all supported branches. Discussion: https://postgr.es/m/18313-64d2c8952d81e84b@postgresql.org
1 parent 5ddf997 commit 25cd2d6

File tree

3 files changed

+29
-4
lines changed

3 files changed

+29
-4
lines changed

src/backend/utils/adt/timestamp.c

+23-4
Original file line numberDiff line numberDiff line change
@@ -3120,8 +3120,16 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
31203120
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
31213121
errmsg("timestamp out of range")));
31223122

3123-
/* Add days by converting to and from Julian */
3124-
julian = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + span->day;
3123+
/*
3124+
* Add days by converting to and from Julian. We need an overflow
3125+
* check here since j2date expects a non-negative integer input.
3126+
*/
3127+
julian = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
3128+
if (pg_add_s32_overflow(julian, span->day, &julian) ||
3129+
julian < 0)
3130+
ereport(ERROR,
3131+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3132+
errmsg("timestamp out of range")));
31253133
j2date(julian, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
31263134

31273135
if (tm2timestamp(tm, fsec, NULL, &timestamp) != 0)
@@ -3256,8 +3264,19 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
32563264
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
32573265
errmsg("timestamp out of range")));
32583266

3259-
/* Add days by converting to and from Julian */
3260-
julian = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + span->day;
3267+
/*
3268+
* Add days by converting to and from Julian. We need an overflow
3269+
* check here since j2date expects a non-negative integer input.
3270+
* In practice though, it will give correct answers for small
3271+
* negative Julian dates; we should allow -1 to avoid
3272+
* timezone-dependent failures, as discussed in timestamp.h.
3273+
*/
3274+
julian = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
3275+
if (pg_add_s32_overflow(julian, span->day, &julian) ||
3276+
julian < -1)
3277+
ereport(ERROR,
3278+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3279+
errmsg("timestamp out of range")));
32613280
j2date(julian, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
32623281

32633282
tz = DetermineTimeZoneOffset(tm, attimezone);

src/test/regress/expected/horology.out

+4
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,8 @@ SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '109203489 days'
482482
Sun Dec 31 00:00:00 294276
483483
(1 row)
484484

485+
SELECT timestamp without time zone '2000-01-01' - interval '2483590 days' AS "out of range";
486+
ERROR: timestamp out of range
485487
SELECT timestamp without time zone '12/31/294276' - timestamp without time zone '12/23/1999' AS "106751991 Days";
486488
106751991 Days
487489
------------------
@@ -742,6 +744,8 @@ SELECT timestamp with time zone '1999-12-01' + interval '1 month - 1 second' AS
742744
Fri Dec 31 23:59:59 1999 PST
743745
(1 row)
744746

747+
SELECT timestamp with time zone '2000-01-01' - interval '2483590 days' AS "out of range";
748+
ERROR: timestamp out of range
745749
SELECT (timestamp with time zone 'today' = (timestamp with time zone 'yesterday' + interval '1 day')) as "True";
746750
True
747751
------

src/test/regress/sql/horology.sql

+2
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ SELECT timestamp without time zone '1999-12-01' + interval '1 month - 1 second'
120120
SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '106000000 days' AS "Feb 23, 285506";
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";
123+
SELECT timestamp without time zone '2000-01-01' - interval '2483590 days' AS "out of range";
123124
SELECT timestamp without time zone '12/31/294276' - timestamp without time zone '12/23/1999' AS "106751991 Days";
124125

125126
-- Shorthand values
@@ -151,6 +152,7 @@ SELECT timestamp with time zone '1996-03-01' - interval '1 second' AS "Feb 29";
151152
SELECT timestamp with time zone '1999-03-01' - interval '1 second' AS "Feb 28";
152153
SELECT timestamp with time zone '2000-03-01' - interval '1 second' AS "Feb 29";
153154
SELECT timestamp with time zone '1999-12-01' + interval '1 month - 1 second' AS "Dec 31";
155+
SELECT timestamp with time zone '2000-01-01' - interval '2483590 days' AS "out of range";
154156

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

0 commit comments

Comments
 (0)