Catch overflow when rounding intervals in AdjustIntervalForTypmod.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 13 Feb 2024 20:58:40 +0000 (15:58 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 13 Feb 2024 20:58:40 +0000 (15:58 -0500)
Previously, an interval microseconds field close to INT64_MAX or
INT64_MIN could overflow, producing a result with not even the
correct sign, while being rounded to match a precision specification.

This seems worth fixing, but not worth back-patching, in part
because the ereturn() notation doesn't exist very far back.

Report and patch by Joseph Koshakow (some cosmetic mods by me)

Discussion: https://postgr.es/m/CAAvxfHfpuLgqJYzkUcher466Z1LpmE+5Sm+zc8L6zKCOQ+6TDQ@mail.gmail.com

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

index c38f88dba7847ad06a42cc60ae8d01c9c67fb949..ed03c50a6dead2d5c6e27d68c76c3199cdfae927 100644 (file)
@@ -1509,17 +1509,23 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod,
 
                        if (interval->time >= INT64CONST(0))
                        {
-                               interval->time = ((interval->time +
-                                                                  IntervalOffsets[precision]) /
-                                                                 IntervalScales[precision]) *
-                                       IntervalScales[precision];
+                               if (pg_add_s64_overflow(interval->time,
+                                                                               IntervalOffsets[precision],
+                                                                               &interval->time))
+                                       ereturn(escontext, false,
+                                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                                        errmsg("interval out of range")));
+                               interval->time -= interval->time % IntervalScales[precision];
                        }
                        else
                        {
-                               interval->time = -(((-interval->time +
-                                                                        IntervalOffsets[precision]) /
-                                                                       IntervalScales[precision]) *
-                                                                  IntervalScales[precision]);
+                               if (pg_sub_s64_overflow(interval->time,
+                                                                               IntervalOffsets[precision],
+                                                                               &interval->time))
+                                       ereturn(escontext, false,
+                                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                                        errmsg("interval out of range")));
+                               interval->time -= interval->time % IntervalScales[precision];
                        }
                }
        }
index b79b6fcd4d7492687893e4bedb6e7694b89e32a6..51ae010c7baba4cb66d65831851c8bcbcb12482c 100644 (file)
@@ -929,6 +929,14 @@ SELECT interval '1 2:03:04.5678' minute to second(2);
  1 day 02:03:04.57
 (1 row)
 
+SELECT interval '2562047788:00:54.775807' second(2);  -- out of range
+ERROR:  interval out of range
+LINE 1: SELECT interval '2562047788:00:54.775807' second(2);
+                        ^
+SELECT interval '-2562047788:00:54.775807' second(2);  -- out of range
+ERROR:  interval out of range
+LINE 1: SELECT interval '-2562047788:00:54.775807' second(2);
+                        ^
 -- test casting to restricted precision (bug #14479)
 SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
   (f1 + INTERVAL '1 month')::INTERVAL MONTH::INTERVAL YEAR AS "years"
index 5566ad0e51674b21a3206db7f4542a28a6c163e2..fbf6e064d66bde02d700a2f8c665ba88eea0745e 100644 (file)
@@ -270,6 +270,8 @@ SELECT interval '1 2:03:04.5678' hour to second(2);
 SELECT interval '1 2.3456' minute to second(2);
 SELECT interval '1 2:03.5678' minute to second(2);
 SELECT interval '1 2:03:04.5678' minute to second(2);
+SELECT interval '2562047788:00:54.775807' second(2);  -- out of range
+SELECT interval '-2562047788:00:54.775807' second(2);  -- out of range
 
 -- test casting to restricted precision (bug #14479)
 SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",