Hi all, Attached is a patch that fixes some overflow/underflow hazards in `timestamp_pl_interval`. The microseconds overflow could produce incorrect result. The month overflow would generally still result in an error from the timestamp month field being too low, but it's still better to catch it early.
I couldn't find any existing timestamp plus interval tests so I stuck a new tests in `timestamp.sql`. If there's a better place, then please let me know. Thanks, Joe Koshakow
From 4350e540fd45d3c868a36021ae79ce533bdeab5f Mon Sep 17 00:00:00 2001 From: Joseph Koshakow <kosh...@gmail.com> Date: Sat, 27 Apr 2024 22:32:44 -0400 Subject: [PATCH] Catch overflow when adding timestamp to interval Previously, an interval microseconds field close to INT64_MAX or an interval months field close to INT32_MAX could overflow when added to a timestamp. The microseconds overflow could produce incorrect result. The month overflow would generally still result in an error from the timestamp month field being too low, but it's still better to catch it early. --- src/backend/utils/adt/timestamp.c | 12 +++++++++--- src/test/regress/expected/timestamp.out | 3 +++ src/test/regress/sql/timestamp.sql | 3 +++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 963f2ec74a..a6b9aeb7b8 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -3091,7 +3091,11 @@ timestamp_pl_interval(PG_FUNCTION_ARGS) (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); - tm->tm_mon += span->month; + if (pg_add_s32_overflow(tm->tm_mon, span->month, &tm->tm_mon)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + if (tm->tm_mon > MONTHS_PER_YEAR) { tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR; @@ -3142,8 +3146,10 @@ timestamp_pl_interval(PG_FUNCTION_ARGS) (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); } - - timestamp += span->time; + if (pg_add_s64_overflow(timestamp, span->time, ×tamp)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); if (!IS_VALID_TIMESTAMP(timestamp)) ereport(ERROR, diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out index cf337da517..fc427baa4a 100644 --- a/src/test/regress/expected/timestamp.out +++ b/src/test/regress/expected/timestamp.out @@ -1230,6 +1230,9 @@ SELECT timestamp '294276-12-31 23:59:59' - timestamp '1999-12-23 19:59:04.224193 SELECT timestamp '294276-12-31 23:59:59' - timestamp '1999-12-23 19:59:04.224192' AS overflows; ERROR: interval out of range +-- test edge-case overflow in timestamp plus interval +SELECT timestamp '294276-12-31 23:59:59' + interval '9223372036854775807 microseconds'; +ERROR: timestamp 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; diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql index 820ef7752a..13baf01d01 100644 --- a/src/test/regress/sql/timestamp.sql +++ b/src/test/regress/sql/timestamp.sql @@ -338,6 +338,9 @@ SELECT extract(epoch from '5000-01-01 00:00:00'::timestamp); 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; +-- test edge-case overflow in timestamp plus interval +SELECT timestamp '294276-12-31 23:59:59' + interval '9223372036854775807 microseconds'; + -- TO_CHAR() SELECT to_char(d1, 'DAY Day day DY Dy dy MONTH Month month RM MON Mon mon') FROM TIMESTAMP_TBL; -- 2.34.1