From b418ee6809558f6119c76103f0361f73c2317821 Mon Sep 17 00:00:00 2001 From: Vitaly Burovoy Date: Thu, 5 Jan 2017 15:52:38 +0000 Subject: [PATCH] Add check for overflow to 'interval' functions Before this commit arithmeric operators could lead to a state when interval_out raised an exception, i.e. you could not dump a table with a bad interval. --- src/backend/utils/adt/timestamp.c | 54 ++++++++++++++++++++++++++++++++-- src/test/regress/expected/interval.out | 22 ++++++++++++++ src/test/regress/sql/interval.sql | 20 +++++++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index a87f982..53336a5 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -1034,6 +1034,29 @@ interval_out(PG_FUNCTION_ARGS) PG_RETURN_CSTRING(result); } +static void +interval_check_overflow(Interval *i) +{ + /* We compare hours with INT_MIN and INT_MAX because "int" type is used + * in interval_out (pg_tm->hour) and can lead to overflow. + * Also make bounds be the same in modulus to be able to use the unary '-' + * operator for negative intervals without possible UB. + */ +#ifdef HAVE_INT64_TIMESTAMP + if (i->time >= ( ((int64)INT_MAX + 1) * SECS_PER_HOUR * USECS_PER_SEC) || + i->time <= (-((int64)INT_MAX + 1) * SECS_PER_HOUR * USECS_PER_SEC)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); +#else + if (i->time >= ( ((int64)INT_MAX + 1) * SECS_PER_HOUR) || + i->time <= (-((int64)INT_MAX + 1) * SECS_PER_HOUR)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); +#endif +} + /* * interval_recv - converts external binary format to interval */ @@ -1058,6 +1081,8 @@ interval_recv(PG_FUNCTION_ARGS) interval->day = pq_getmsgint(buf, sizeof(interval->day)); interval->month = pq_getmsgint(buf, sizeof(interval->month)); + interval_check_overflow(interval); + AdjustIntervalForTypmod(interval, typmod); PG_RETURN_INTERVAL_P(interval); @@ -1610,6 +1635,9 @@ make_interval(PG_FUNCTION_ARGS) double secs = PG_GETARG_FLOAT8(6); Interval *result; + int32 years_month; + int32 weeks_days; + /* * Reject out-of-range inputs. We really ought to check the integer * inputs as well, but it's not entirely clear what limits to apply. @@ -1619,9 +1647,23 @@ make_interval(PG_FUNCTION_ARGS) (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("interval out of range"))); + years_month = years * MONTHS_PER_YEAR; + weeks_days = weeks * 7; + + if((years_month / MONTHS_PER_YEAR != years) || (weeks_days / 7 != weeks)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + result = (Interval *) palloc(sizeof(Interval)); - result->month = years * MONTHS_PER_YEAR + months; - result->day = weeks * 7 + days; + result->month = years_month + months; + result->day = weeks_days + days; + + if ((SAMESIGN(years_month, months) && !SAMESIGN(result->month, months)) || + (SAMESIGN(weeks_days, days) && !SAMESIGN(result->day, days))) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); secs += hours * (double) SECS_PER_HOUR + mins * (double) SECS_PER_MINUTE; @@ -1631,6 +1673,8 @@ make_interval(PG_FUNCTION_ARGS) result->time = secs; #endif + interval_check_overflow(result); + PG_RETURN_INTERVAL_P(result); } @@ -3383,6 +3427,8 @@ interval_pl(PG_FUNCTION_ARGS) (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("interval out of range"))); + interval_check_overflow(result); + PG_RETURN_INTERVAL_P(result); } @@ -3417,6 +3463,8 @@ interval_mi(PG_FUNCTION_ARGS) (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("interval out of range"))); + interval_check_overflow(result); + PG_RETURN_INTERVAL_P(result); } @@ -3504,6 +3552,8 @@ interval_mul(PG_FUNCTION_ARGS) result->time = span->time * factor + sec_remainder; #endif + interval_check_overflow(result); + PG_RETURN_INTERVAL_P(result); } diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out index 946c97a..9b85ad5 100644 --- a/src/test/regress/expected/interval.out +++ b/src/test/regress/expected/interval.out @@ -863,3 +863,25 @@ select make_interval(secs := 7e12); @ 1944444444 hours 26 mins 40 secs (1 row) +-- overflow check +-- To be sure an error is raised by an internal check (not output) function, INSERT is used instead of SELECT. +CREATE TABLE interval_chk(i interval); +INSERT INTO interval_chk VALUES(7730941132799 * interval '1 second'); -- ok, maximal allowed value +INSERT INTO interval_chk VALUES(7730941132800 * interval '1 second'); +ERROR: interval out of range +INSERT INTO interval_chk VALUES(-7730941132799 * interval '1 second'); -- ok, minimal allowed value +INSERT INTO interval_chk VALUES(-7730941132800 * interval '1 second'); +ERROR: interval out of range +INSERT INTO interval_chk VALUES('2147483647:59:59'::interval); -- OK +INSERT INTO interval_chk VALUES('-2147483647:59:59'::interval); -- OK +INSERT INTO interval_chk VALUES('2147483647:59:59'::interval + '1s'::interval); +ERROR: interval out of range +INSERT INTO interval_chk VALUES('-2147483647:59:59'::interval - '1s'::interval); +ERROR: interval out of range +INSERT INTO interval_chk VALUES(make_interval(hours => 2147483647, secs => 3599)); --OK +INSERT INTO interval_chk VALUES(make_interval(hours => 2147483647, secs => 3600)); +ERROR: interval out of range +INSERT INTO interval_chk VALUES(make_interval(hours => -2147483647, secs => -3599)); --OK +INSERT INTO interval_chk VALUES(make_interval(hours => -2147483647, secs => -3600)); +ERROR: interval out of range +DROP TABLE interval_chk; diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql index cff9ada..843838a 100644 --- a/src/test/regress/sql/interval.sql +++ b/src/test/regress/sql/interval.sql @@ -281,3 +281,23 @@ select make_interval(months := 'NaN'::float::int); select make_interval(secs := 'inf'); select make_interval(secs := 'NaN'); select make_interval(secs := 7e12); + +-- overflow check +-- To be sure an error is raised by an internal check (not output) function, INSERT is used instead of SELECT. +CREATE TABLE interval_chk(i interval); +INSERT INTO interval_chk VALUES(7730941132799 * interval '1 second'); -- ok, maximal allowed value +INSERT INTO interval_chk VALUES(7730941132800 * interval '1 second'); +INSERT INTO interval_chk VALUES(-7730941132799 * interval '1 second'); -- ok, minimal allowed value +INSERT INTO interval_chk VALUES(-7730941132800 * interval '1 second'); + +INSERT INTO interval_chk VALUES('2147483647:59:59'::interval); -- OK +INSERT INTO interval_chk VALUES('-2147483647:59:59'::interval); -- OK + +INSERT INTO interval_chk VALUES('2147483647:59:59'::interval + '1s'::interval); +INSERT INTO interval_chk VALUES('-2147483647:59:59'::interval - '1s'::interval); + +INSERT INTO interval_chk VALUES(make_interval(hours => 2147483647, secs => 3599)); --OK +INSERT INTO interval_chk VALUES(make_interval(hours => 2147483647, secs => 3600)); +INSERT INTO interval_chk VALUES(make_interval(hours => -2147483647, secs => -3599)); --OK +INSERT INTO interval_chk VALUES(make_interval(hours => -2147483647, secs => -3600)); +DROP TABLE interval_chk; -- 2.10.2