From 3850d4dec1d91c4fdce274f42986840444d5593e Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Thu, 9 Nov 2023 12:10:14 +0000 Subject: [PATCH] Avoid integer overflow hazard in interval_time(). When casting an interval to a time, the original code suffered from 64-bit integer overflow for inputs with a sufficiently large negative "time" field, leading to bogus results. Fix by rewriting the algorithm in a simpler form, that more obviously cannot overflow. While at it, improve the test coverage to include negative interval inputs. Discussion: https://postgr.es/m/CAEZATCXoUKHkcuq4q63hkiPsKZJd0kZWzgKtU%2BNT0aU4wbf_Pw%40mail.gmail.com --- src/backend/utils/adt/date.c | 15 +++------------ src/test/regress/expected/horology.out | 12 ++++++++++++ src/test/regress/sql/horology.sql | 2 ++ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 56c7746c11f..544e1d32bfc 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -2012,19 +2012,10 @@ interval_time(PG_FUNCTION_ARGS) { Interval *span = PG_GETARG_INTERVAL_P(0); TimeADT result; - int64 days; - result = span->time; - if (result >= USECS_PER_DAY) - { - days = result / USECS_PER_DAY; - result -= days * USECS_PER_DAY; - } - else if (result < 0) - { - days = (-result + USECS_PER_DAY - 1) / USECS_PER_DAY; - result += days * USECS_PER_DAY; - } + result = span->time % USECS_PER_DAY; + if (result < 0) + result += USECS_PER_DAY; PG_RETURN_TIMEADT(result); } diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out index e63e5b30fee..8f52661096f 100644 --- a/src/test/regress/expected/horology.out +++ b/src/test/regress/expected/horology.out @@ -981,6 +981,18 @@ SELECT CAST(interval '02:03' AS time) AS "02:03:00"; 02:03:00 (1 row) +SELECT CAST(interval '-02:03' AS time) AS "21:57:00"; + 21:57:00 +---------- + 21:57:00 +(1 row) + +SELECT CAST(interval '-9223372022400000000 us' AS time) AS "00:00:00"; + 00:00:00 +---------- + 00:00:00 +(1 row) + SELECT time '01:30' + interval '02:01' AS "03:31:00"; 03:31:00 ---------- diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql index f7f8c8d2dd9..39a35a6b7ce 100644 --- a/src/test/regress/sql/horology.sql +++ b/src/test/regress/sql/horology.sql @@ -182,6 +182,8 @@ SELECT d1 - interval '1 year' AS one_year FROM TIMESTAMPTZ_TBL; SELECT CAST(time '01:02' AS interval) AS "+01:02"; SELECT CAST(interval '02:03' AS time) AS "02:03:00"; +SELECT CAST(interval '-02:03' AS time) AS "21:57:00"; +SELECT CAST(interval '-9223372022400000000 us' AS time) AS "00:00:00"; SELECT time '01:30' + interval '02:01' AS "03:31:00"; SELECT time '01:30' - interval '02:01' AS "23:29:00"; SELECT time '02:30' + interval '36:01' AS "14:31:00";