Skip to content

Commit

Permalink
Guard against overflow in make_interval().
Browse files Browse the repository at this point in the history
The original code did very little to guard against integer or floating
point overflow when computing the interval's fields.  Detect any such
overflows and error out, rather than silently returning bogus results.

Joseph Koshakow, reviewed by Ashutosh Bapat and me.

Discussion: https://postgr.es/m/CAAvxfHcm1TPwH_zaGWuFoL8pZBestbRZTU6Z%3D-RvAdSXTPbKfg%40mail.gmail.com
  • Loading branch information
deanrasheed committed Oct 29, 2023
1 parent 849172f commit b2d5544
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 11 deletions.
39 changes: 28 additions & 11 deletions src/backend/utils/adt/timestamp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1509,24 +1509,41 @@ make_interval(PG_FUNCTION_ARGS)
Interval *result;

/*
* 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.
* Reject out-of-range inputs. We reject any input values that cause
* integer overflow of the corresponding interval fields.
*/
if (isinf(secs) || isnan(secs))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
goto out_of_range;

result = (Interval *) palloc(sizeof(Interval));
result->month = years * MONTHS_PER_YEAR + months;
result->day = weeks * 7 + days;

secs = rint(secs * USECS_PER_SEC);
result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
(int64) secs;
/* years and months -> months */
if (pg_mul_s32_overflow(years, MONTHS_PER_YEAR, &result->month) ||
pg_add_s32_overflow(result->month, months, &result->month))
goto out_of_range;

/* weeks and days -> days */
if (pg_mul_s32_overflow(weeks, DAYS_PER_WEEK, &result->day) ||
pg_add_s32_overflow(result->day, days, &result->day))
goto out_of_range;

/* hours and mins -> usecs (cannot overflow 64-bit) */
result->time = hours * USECS_PER_HOUR + mins * USECS_PER_MINUTE;

/* secs -> usecs */
secs = rint(float8_mul(secs, USECS_PER_SEC));
if (!FLOAT8_FITS_IN_INT64(secs) ||
pg_add_s64_overflow(result->time, (int64) secs, &result->time))
goto out_of_range;

PG_RETURN_INTERVAL_P(result);

out_of_range:
ereport(ERROR,
errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range"));

PG_RETURN_NULL(); /* keep compiler quiet */
}

/* EncodeSpecialTimestamp()
Expand Down
1 change: 1 addition & 0 deletions src/include/datatype/timestamp.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */

/*
Expand Down
27 changes: 27 additions & 0 deletions src/test/regress/expected/interval.out
Original file line number Diff line number Diff line change
Expand Up @@ -1587,6 +1587,33 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
-- overflowing using make_interval
select make_interval(years := 178956971);
ERROR: interval out of range
select make_interval(years := -178956971);
ERROR: interval out of range
select make_interval(years := 1, months := 2147483647);
ERROR: interval out of range
select make_interval(years := -1, months := -2147483648);
ERROR: interval out of range
select make_interval(weeks := 306783379);
ERROR: interval out of range
select make_interval(weeks := -306783379);
ERROR: interval out of range
select make_interval(weeks := 1, days := 2147483647);
ERROR: interval out of range
select make_interval(weeks := -1, days := -2147483648);
ERROR: interval out of range
select make_interval(secs := 1e308);
ERROR: value out of range: overflow
select make_interval(secs := 1e18);
ERROR: interval out of range
select make_interval(secs := -1e18);
ERROR: interval out of range
select make_interval(mins := 1, secs := 9223372036800.0);
ERROR: interval out of range
select make_interval(mins := -1, secs := -9223372036800.0);
ERROR: interval out of range
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
Expand Down
15 changes: 15 additions & 0 deletions src/test/regress/sql/interval.sql
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,21 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';

-- overflowing using make_interval
select make_interval(years := 178956971);
select make_interval(years := -178956971);
select make_interval(years := 1, months := 2147483647);
select make_interval(years := -1, months := -2147483648);
select make_interval(weeks := 306783379);
select make_interval(weeks := -306783379);
select make_interval(weeks := 1, days := 2147483647);
select make_interval(weeks := -1, days := -2147483648);
select make_interval(secs := 1e308);
select make_interval(secs := 1e18);
select make_interval(secs := -1e18);
select make_interval(mins := 1, secs := 9223372036800.0);
select make_interval(mins := -1, secs := -9223372036800.0);

-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
Expand Down

0 comments on commit b2d5544

Please sign in to comment.