From 5ebc9c90173f32cffe373a80835f157b9ebfa3bd Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 13 Feb 2024 15:58:40 -0500 Subject: [PATCH] Catch overflow when rounding intervals in AdjustIntervalForTypmod. 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 | 22 ++++++++++++++-------- src/test/regress/expected/interval.out | 8 ++++++++ src/test/regress/sql/interval.sql | 2 ++ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index c38f88dba7..ed03c50a6d 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -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]; } } } diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out index b79b6fcd4d..51ae010c7b 100644 --- a/src/test/regress/expected/interval.out +++ b/src/test/regress/expected/interval.out @@ -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" diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql index 5566ad0e51..fbf6e064d6 100644 --- a/src/test/regress/sql/interval.sql +++ b/src/test/regress/sql/interval.sql @@ -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",