Detect overflow in timestamp[tz] subtraction.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 20 Feb 2023 22:26:25 +0000 (17:26 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 20 Feb 2023 22:26:25 +0000 (17:26 -0500)
It's possible to overflow the int64 microseconds field of the
output interval when subtracting two timestamps.  Detect that
instead of silently returning a bogus result.

Nick Babadzhanian

Discussion: https://postgr.es/m/CABw73Uq2oJ3E+kYvvDuY04EkhhkChim2e-PaghBDjOmgUAMWGw@mail.gmail.com

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

index 47e059a409b98c25647b975917e8c9d0c876687d..de93db89d4838986a9748ee381f16a72d8acf121 100644 (file)
@@ -2713,7 +2713,10 @@ timestamp_mi(PG_FUNCTION_ARGS)
                                (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                                 errmsg("cannot subtract infinite timestamps")));
 
-       result->time = dt1 - dt2;
+       if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("interval out of range")));
 
        result->month = 0;
        result->day = 0;
index edc6912e7abefe3229b3e7644b9caf983f58216f..eef2f7001c3baf4a9737da52a102f583384c0616 100644 (file)
@@ -1207,6 +1207,15 @@ SELECT extract(epoch from '5000-01-01 00:00:00'::timestamp);
  95617584000.000000
 (1 row)
 
+-- test edge-case overflow in timestamp subtraction
+SELECT timestamp '294276-12-31 23:59:59' - timestamp '1999-12-23 19:59:04.224193' AS ok;
+                   ok                    
+-----------------------------------------
+ @ 106751991 days 4 hours 54.775807 secs
+(1 row)
+
+SELECT timestamp '294276-12-31 23:59:59' - timestamp '1999-12-23 19:59:04.224192' AS overflows;
+ERROR:  interval out of range
 -- TO_CHAR()
 SELECT to_char(d1, 'DAY Day day DY Dy dy MONTH Month month RM MON Mon mon')
    FROM TIMESTAMP_TBL;
index 00379fd0fd315eb9cf3c4a5b3608b49a0608740d..b85a93a3c2901da7dc195675d4dda4af2ff3b940 100644 (file)
@@ -1331,6 +1331,15 @@ SELECT extract(epoch from '5000-01-01 00:00:00+00'::timestamptz);
  95617584000.000000
 (1 row)
 
+-- test edge-case overflow in timestamp subtraction
+SELECT timestamptz '294276-12-31 23:59:59 UTC' - timestamptz '1999-12-23 19:59:04.224193 UTC' AS ok;
+                   ok                    
+-----------------------------------------
+ @ 106751991 days 4 hours 54.775807 secs
+(1 row)
+
+SELECT timestamptz '294276-12-31 23:59:59 UTC' - timestamptz '1999-12-23 19:59:04.224192 UTC' AS overflows;
+ERROR:  interval out of range
 -- TO_CHAR()
 SELECT to_char(d1, 'DAY Day day DY Dy dy MONTH Month month RM MON Mon mon')
    FROM TIMESTAMPTZ_TBL;
index 1d580f77f15fe3ca794fe66487967b61dc14f2cf..2d5f01ab8686a2783323aa31088fb066290e5bea 100644 (file)
@@ -326,6 +326,10 @@ SELECT extract(epoch from '294270-01-01 00:00:00'::timestamp);
 -- another internal overflow test case
 SELECT extract(epoch from '5000-01-01 00:00:00'::timestamp);
 
+-- test edge-case overflow in timestamp subtraction
+SELECT timestamp '294276-12-31 23:59:59' - timestamp '1999-12-23 19:59:04.224193' AS ok;
+SELECT timestamp '294276-12-31 23:59:59' - timestamp '1999-12-23 19:59:04.224192' AS overflows;
+
 -- TO_CHAR()
 SELECT to_char(d1, 'DAY Day day DY Dy dy MONTH Month month RM MON Mon mon')
    FROM TIMESTAMP_TBL;
index 4905dd083177fd7695c2f50c888ea12781176431..6d10937d863b633142595999ee4ca2617bcc87de 100644 (file)
@@ -306,6 +306,10 @@ SELECT extract(epoch from '294270-01-01 00:00:00+00'::timestamptz);
 -- another internal overflow test case
 SELECT extract(epoch from '5000-01-01 00:00:00+00'::timestamptz);
 
+-- test edge-case overflow in timestamp subtraction
+SELECT timestamptz '294276-12-31 23:59:59 UTC' - timestamptz '1999-12-23 19:59:04.224193 UTC' AS ok;
+SELECT timestamptz '294276-12-31 23:59:59 UTC' - timestamptz '1999-12-23 19:59:04.224192 UTC' AS overflows;
+
 -- TO_CHAR()
 SELECT to_char(d1, 'DAY Day day DY Dy dy MONTH Month month RM MON Mon mon')
    FROM TIMESTAMPTZ_TBL;