From f7f09855722bf0ae2b211dc4f99fe4a66870ef11 Mon Sep 17 00:00:00 2001 From: Rahul Yadav Date: Sat, 26 Sep 2026 15:44:06 +0100 Subject: [PATCH v1] Fix integer overflow in time and timetz interval arithmetic time_pl_interval() and time_mi_interval(), and their timetz counterparts, added the interval's time field to the time value before reducing the result modulo one day. A large enough interval made that addition overflow int64, which is undefined behavior; in practice it produced a wrong time of day without any error. For example, SELECT time '23:59:59.999999' + interval '9223372036854 seconds'; returned 19:59:04.448383 instead of 04:00:53.999999. Since the result wraps around at midnight, only the interval's time field modulo one day matters. Reduce it modulo USECS_PER_DAY before adding or subtracting, so the intermediate result always fits in an int64. Results for intervals that did not overflow are unchanged. Add regression tests using the largest and smallest possible interval time fields. Author: Rahul Yadav Reported-by: Tianyu Shi <1950233439@qq.com> Bug: #19670 Discussion: https://postgr.es/m/19670-c4e56832fa6686f8@postgresql.org Backpatch-through: 14 --- src/backend/utils/adt/date.c | 16 ++++++++++++---- src/test/regress/expected/interval.out | 25 +++++++++++++++++++++++++ src/test/regress/sql/interval.sql | 6 ++++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 7f746dd84c..b30b570ab1 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -2176,7 +2176,12 @@ time_pl_interval(PG_FUNCTION_ARGS) (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("cannot add infinite interval to time"))); - result = time + span->time; + /* + * The result wraps around at midnight, so only the interval's time field + * modulo one day matters. Reducing it first also prevents integer + * overflow when the interval is very large. + */ + result = time + (span->time % USECS_PER_DAY); result -= result / USECS_PER_DAY * USECS_PER_DAY; if (result < INT64CONST(0)) result += USECS_PER_DAY; @@ -2200,7 +2205,8 @@ time_mi_interval(PG_FUNCTION_ARGS) (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("cannot subtract infinite interval from time"))); - result = time - span->time; + /* As in time_pl_interval, reduce modulo one day to prevent overflow */ + result = time - (span->time % USECS_PER_DAY); result -= result / USECS_PER_DAY * USECS_PER_DAY; if (result < INT64CONST(0)) result += USECS_PER_DAY; @@ -2728,7 +2734,8 @@ timetz_pl_interval(PG_FUNCTION_ARGS) result = palloc_object(TimeTzADT); - result->time = time->time + span->time; + /* As in time_pl_interval, reduce modulo one day to prevent overflow */ + result->time = time->time + (span->time % USECS_PER_DAY); result->time -= result->time / USECS_PER_DAY * USECS_PER_DAY; if (result->time < INT64CONST(0)) result->time += USECS_PER_DAY; @@ -2756,7 +2763,8 @@ timetz_mi_interval(PG_FUNCTION_ARGS) result = palloc_object(TimeTzADT); - result->time = time->time - span->time; + /* As in time_pl_interval, reduce modulo one day to prevent overflow */ + result->time = time->time - (span->time % USECS_PER_DAY); result->time -= result->time / USECS_PER_DAY * USECS_PER_DAY; if (result->time < INT64CONST(0)) result->time += USECS_PER_DAY; diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out index a16e3ccdb2..5ae1a4897b 100644 --- a/src/test/regress/expected/interval.out +++ b/src/test/regress/expected/interval.out @@ -2128,6 +2128,31 @@ SELECT timetz '11:27:42' - interval 'infinity'; ERROR: cannot subtract infinite interval from time SELECT timetz '11:27:42' - interval '-infinity'; ERROR: cannot subtract infinite interval from time +-- time +/- interval must not overflow, however large the interval +SELECT time '23:59:59.999999' + interval '9223372036854775807 microseconds'; + ?column? +----------------- + 04:00:54.775806 +(1 row) + +SELECT time '23:59:59.999999' - interval '-9223372036854775808 microseconds'; + ?column? +----------------- + 04:00:54.775807 +(1 row) + +SELECT timetz '23:59:59.999999+01' + interval '9223372036854775807 microseconds'; + ?column? +-------------------- + 04:00:54.775806+01 +(1 row) + +SELECT timetz '23:59:59.999999+01' - interval '-9223372036854775808 microseconds'; + ?column? +-------------------- + 04:00:54.775807+01 +(1 row) + SELECT lhst.i lhs, rhst.i rhs, lhst.i < rhst.i AS lt, diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql index 43bc793925..5ab8a6dcea 100644 --- a/src/test/regress/sql/interval.sql +++ b/src/test/regress/sql/interval.sql @@ -728,6 +728,12 @@ SELECT timetz '11:27:42' + interval '-infinity'; SELECT timetz '11:27:42' - interval 'infinity'; SELECT timetz '11:27:42' - interval '-infinity'; +-- time +/- interval must not overflow, however large the interval +SELECT time '23:59:59.999999' + interval '9223372036854775807 microseconds'; +SELECT time '23:59:59.999999' - interval '-9223372036854775808 microseconds'; +SELECT timetz '23:59:59.999999+01' + interval '9223372036854775807 microseconds'; +SELECT timetz '23:59:59.999999+01' - interval '-9223372036854775808 microseconds'; + SELECT lhst.i lhs, rhst.i rhs, lhst.i < rhst.i AS lt, -- 2.50.1 (Apple Git-155)