Hi all, Immediately after sending this I realized that timestamptz suffers from the same problem. Attached is an updated patch that fixes timestamptz too.
Thanks, Joe Koshakow On Sat, Apr 27, 2024 at 10:59 PM Joseph Koshakow <kosh...@gmail.com> wrote: > 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 1a039ab807654fe9b7a2043e30ecdee925127d77 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 or timestamptz. The microseconds overflow could produce incorrect results. The month overflow would generally still result in an error from the resulting month field being too low, but it's still better to catch it early. --- src/backend/utils/adt/timestamp.c | 21 +++++++++++++++++---- src/test/regress/expected/timestamp.out | 3 +++ src/test/regress/expected/timestamptz.out | 3 +++ src/test/regress/sql/timestamp.sql | 3 +++ src/test/regress/sql/timestamptz.sql | 3 +++ 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 963f2ec74a..551c0dbd7a 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -3091,7 +3091,10 @@ 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; @@ -3143,7 +3146,10 @@ timestamp_pl_interval(PG_FUNCTION_ARGS) 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, @@ -3233,7 +3239,10 @@ timestamptz_pl_interval_internal(TimestampTz timestamp, (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; @@ -3292,7 +3301,11 @@ timestamptz_pl_interval_internal(TimestampTz timestamp, 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/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out index bfb3825ff6..143aaeb126 100644 --- a/src/test/regress/expected/timestamptz.out +++ b/src/test/regress/expected/timestamptz.out @@ -1354,6 +1354,9 @@ SELECT timestamptz '294276-12-31 23:59:59 UTC' - timestamptz '1999-12-23 19:59:0 SELECT timestamptz '294276-12-31 23:59:59 UTC' - timestamptz '1999-12-23 19:59:04.224192 UTC' AS overflows; ERROR: interval out of range +-- test edge-case overflow in timestamp plus interval +SELECT timestamptz '294276-12-31 23:59:59 UTC' + 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 TIMESTAMPTZ_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; diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql index ccfd90d646..82e9a3cf88 100644 --- a/src/test/regress/sql/timestamptz.sql +++ b/src/test/regress/sql/timestamptz.sql @@ -318,6 +318,9 @@ SELECT extract(epoch from '5000-01-01 00:00:00+00'::timestamptz); SELECT timestamptz '294276-12-31 23:59:59 UTC' - timestamptz '1999-12-23 19:59:04.224193 UTC' AS ok; SELECT timestamptz '294276-12-31 23:59:59 UTC' - timestamptz '1999-12-23 19:59:04.224192 UTC' AS overflows; +-- test edge-case overflow in timestamp plus interval +SELECT timestamptz '294276-12-31 23:59:59 UTC' + interval '9223372036854775807 microseconds'; + -- TO_CHAR() SELECT to_char(d1, 'DAY Day day DY Dy dy MONTH Month month RM MON Mon mon') FROM TIMESTAMPTZ_TBL; -- 2.34.1