Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Detect more overflows in timestamp[tz]_pl_interval.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 28 Apr 2024 17:42:13 +0000 (13:42 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 28 Apr 2024 17:42:13 +0000 (13:42 -0400)
In commit 25cd2d640 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

src/backend/utils/adt/timestamp.c
src/test/regress/expected/horology.out
src/test/regress/sql/horology.sql

index 58348f914b0c31a0063b0f0087ef7b6083be7435..1544d54460efa18a5976bf643b4804b6e9e18f00 100644 (file)
@@ -2915,7 +2915,10 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
                        (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                         errmsg("timestamp out of range")));
 
-           tm->tm_mon += span->month;
+           if (pg_add_s32_overflow(tm->tm_mon, span->month, &tm->tm_mon))
+               ereport(ERROR,
+                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                        errmsg("timestamp out of range")));
            if (tm->tm_mon > MONTHS_PER_YEAR)
            {
                tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR;
@@ -2967,7 +2970,10 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
                         errmsg("timestamp out of range")));
        }
 
-       timestamp += span->time;
+       if (pg_add_s64_overflow(timestamp, span->time, &timestamp))
+           ereport(ERROR,
+                   (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                    errmsg("timestamp out of range")));
 
        if (!IS_VALID_TIMESTAMP(timestamp))
            ereport(ERROR,
@@ -3029,7 +3035,10 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
                        (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                         errmsg("timestamp out of range")));
 
-           tm->tm_mon += span->month;
+           if (pg_add_s32_overflow(tm->tm_mon, span->month, &tm->tm_mon))
+               ereport(ERROR,
+                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                        errmsg("timestamp out of range")));
            if (tm->tm_mon > MONTHS_PER_YEAR)
            {
                tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR;
@@ -3088,7 +3097,10 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
                         errmsg("timestamp out of range")));
        }
 
-       timestamp += span->time;
+       if (pg_add_s64_overflow(timestamp, span->time, &timestamp))
+           ereport(ERROR,
+                   (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                    errmsg("timestamp out of range")));
 
        if (!IS_VALID_TIMESTAMP(timestamp))
            ereport(ERROR,
index 2dc9b00dbd691224e1e33cb95d88a676c3693923..735352976c78caa5976f3a9a4b674706a13f239f 100644 (file)
@@ -375,6 +375,8 @@ SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '109203489 days'
 
 SELECT timestamp without time zone '2000-01-01' - interval '2483590 days' AS "out of range";
 ERROR:  timestamp out of range
+SELECT timestamp without time zone '294276-12-31 23:59:59' + interval '9223372036854775807 microseconds' AS "out of range";
+ERROR:  timestamp out of range
 SELECT timestamp without time zone '12/31/294276' - timestamp without time zone '12/23/1999' AS "106751991 Days";
   106751991 Days  
 ------------------
@@ -637,6 +639,8 @@ SELECT timestamp with time zone '1999-12-01' + interval '1 month - 1 second' AS
 
 SELECT timestamp with time zone '2000-01-01' - interval '2483590 days' AS "out of range";
 ERROR:  timestamp out of range
+SELECT timestamp with time zone '294276-12-31 23:59:59 UTC' + interval '9223372036854775807 microseconds' AS "out of range";
+ERROR:  timestamp out of range
 SELECT (timestamp with time zone 'today' = (timestamp with time zone 'yesterday' + interval '1 day')) as "True";
  True 
 ------
index 40653481f1de4abc5c43b389e989c60ccf72ff74..7d6153a432937006335d0ba3ea334aa22d6199c0 100644 (file)
@@ -87,6 +87,7 @@ SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '106000000 days'
 SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '107000000 days' AS "Jan 20, 288244";
 SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '109203489 days' AS "Dec 31, 294276";
 SELECT timestamp without time zone '2000-01-01' - interval '2483590 days' AS "out of range";
+SELECT timestamp without time zone '294276-12-31 23:59:59' + interval '9223372036854775807 microseconds' AS "out of range";
 SELECT timestamp without time zone '12/31/294276' - timestamp without time zone '12/23/1999' AS "106751991 Days";
 
 -- Shorthand values
@@ -119,6 +120,7 @@ SELECT timestamp with time zone '1999-03-01' - interval '1 second' AS "Feb 28";
 SELECT timestamp with time zone '2000-03-01' - interval '1 second' AS "Feb 29";
 SELECT timestamp with time zone '1999-12-01' + interval '1 month - 1 second' AS "Dec 31";
 SELECT timestamp with time zone '2000-01-01' - interval '2483590 days' AS "out of range";
+SELECT timestamp with time zone '294276-12-31 23:59:59 UTC' + interval '9223372036854775807 microseconds' AS "out of range";
 
 SELECT (timestamp with time zone 'today' = (timestamp with time zone 'yesterday' + interval '1 day')) as "True";
 SELECT (timestamp with time zone 'today' = (timestamp with time zone 'tomorrow' - interval '1 day')) as "True";