Skip to content

Commit f499d2b

Browse files
committed
Guard against overflow in interval_mul() and interval_div().
Commits 146604e and a898b40 added overflow checks to interval_mul(), but not to interval_div(), which contains almost identical code, and so is susceptible to the same kinds of overflows. In addition, those checks did not catch all possible overflow conditions. Add additional checks to the "cascade down" code in interval_mul(), and copy all the overflow checks over to the corresponding code in interval_div(), so that they both generate "interval out of range" errors, rather than returning bogus results. Given that these errors are relatively easy to hit, back-patch to all supported branches. Per bug #18200 from Alexander Lakhin, and subsequent investigation. Discussion: https://postgr.es/m/18200-5ea288c7b2d504b1%40postgresql.org
1 parent 3b991f8 commit f499d2b

File tree

3 files changed

+70
-21
lines changed

3 files changed

+70
-21
lines changed

src/backend/utils/adt/timestamp.c

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
#include "access/xact.h"
2424
#include "catalog/pg_type.h"
25+
#include "common/int.h"
2526
#include "common/int128.h"
2627
#include "funcapi.h"
2728
#include "libpq/pqformat.h"
@@ -3189,19 +3190,13 @@ interval_mul(PG_FUNCTION_ARGS)
31893190
result = (Interval *) palloc(sizeof(Interval));
31903191

31913192
result_double = span->month * factor;
3192-
if (isnan(result_double) ||
3193-
result_double > INT_MAX || result_double < INT_MIN)
3194-
ereport(ERROR,
3195-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3196-
errmsg("interval out of range")));
3193+
if (isnan(result_double) || !FLOAT8_FITS_IN_INT32(result_double))
3194+
goto out_of_range;
31973195
result->month = (int32) result_double;
31983196

31993197
result_double = span->day * factor;
3200-
if (isnan(result_double) ||
3201-
result_double > INT_MAX || result_double < INT_MIN)
3202-
ereport(ERROR,
3203-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3204-
errmsg("interval out of range")));
3198+
if (isnan(result_double) || !FLOAT8_FITS_IN_INT32(result_double))
3199+
goto out_of_range;
32053200
result->day = (int32) result_double;
32063201

32073202
/*
@@ -3235,20 +3230,30 @@ interval_mul(PG_FUNCTION_ARGS)
32353230
*/
32363231
if (Abs(sec_remainder) >= SECS_PER_DAY)
32373232
{
3238-
result->day += (int) (sec_remainder / SECS_PER_DAY);
3233+
if (pg_add_s32_overflow(result->day,
3234+
(int) (sec_remainder / SECS_PER_DAY),
3235+
&result->day))
3236+
goto out_of_range;
32393237
sec_remainder -= (int) (sec_remainder / SECS_PER_DAY) * SECS_PER_DAY;
32403238
}
32413239

32423240
/* cascade units down */
3243-
result->day += (int32) month_remainder_days;
3241+
if (pg_add_s32_overflow(result->day, (int32) month_remainder_days,
3242+
&result->day))
3243+
goto out_of_range;
32443244
result_double = rint(span->time * factor + sec_remainder * USECS_PER_SEC);
32453245
if (isnan(result_double) || !FLOAT8_FITS_IN_INT64(result_double))
3246-
ereport(ERROR,
3247-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3248-
errmsg("interval out of range")));
3246+
goto out_of_range;
32493247
result->time = (int64) result_double;
32503248

32513249
PG_RETURN_INTERVAL_P(result);
3250+
3251+
out_of_range:
3252+
ereport(ERROR,
3253+
errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3254+
errmsg("interval out of range"));
3255+
3256+
PG_RETURN_NULL(); /* keep compiler quiet */
32523257
}
32533258

32543259
Datum
@@ -3267,7 +3272,8 @@ interval_div(PG_FUNCTION_ARGS)
32673272
Interval *span = PG_GETARG_INTERVAL_P(0);
32683273
float8 factor = PG_GETARG_FLOAT8(1);
32693274
double month_remainder_days,
3270-
sec_remainder;
3275+
sec_remainder,
3276+
result_double;
32713277
int32 orig_month = span->month,
32723278
orig_day = span->day;
32733279
Interval *result;
@@ -3279,8 +3285,15 @@ interval_div(PG_FUNCTION_ARGS)
32793285
(errcode(ERRCODE_DIVISION_BY_ZERO),
32803286
errmsg("division by zero")));
32813287

3282-
result->month = (int32) (span->month / factor);
3283-
result->day = (int32) (span->day / factor);
3288+
result_double = span->month / factor;
3289+
if (isnan(result_double) || !FLOAT8_FITS_IN_INT32(result_double))
3290+
goto out_of_range;
3291+
result->month = (int32) result_double;
3292+
3293+
result_double = span->day / factor;
3294+
if (isnan(result_double) || !FLOAT8_FITS_IN_INT32(result_double))
3295+
goto out_of_range;
3296+
result->day = (int32) result_double;
32843297

32853298
/*
32863299
* Fractional months full days into days. See comment in interval_mul().
@@ -3292,15 +3305,30 @@ interval_div(PG_FUNCTION_ARGS)
32923305
sec_remainder = TSROUND(sec_remainder);
32933306
if (Abs(sec_remainder) >= SECS_PER_DAY)
32943307
{
3295-
result->day += (int) (sec_remainder / SECS_PER_DAY);
3308+
if (pg_add_s32_overflow(result->day,
3309+
(int) (sec_remainder / SECS_PER_DAY),
3310+
&result->day))
3311+
goto out_of_range;
32963312
sec_remainder -= (int) (sec_remainder / SECS_PER_DAY) * SECS_PER_DAY;
32973313
}
32983314

32993315
/* cascade units down */
3300-
result->day += (int32) month_remainder_days;
3301-
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
3316+
if (pg_add_s32_overflow(result->day, (int32) month_remainder_days,
3317+
&result->day))
3318+
goto out_of_range;
3319+
result_double = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
3320+
if (isnan(result_double) || !FLOAT8_FITS_IN_INT64(result_double))
3321+
goto out_of_range;
3322+
result->time = (int64) result_double;
33023323

33033324
PG_RETURN_INTERVAL_P(result);
3325+
3326+
out_of_range:
3327+
ereport(ERROR,
3328+
errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3329+
errmsg("interval out of range"));
3330+
3331+
PG_RETURN_NULL(); /* keep compiler quiet */
33043332
}
33053333

33063334

src/test/regress/expected/interval.out

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,19 @@ SELECT '' AS ten, * FROM INTERVAL_TBL;
357357
| @ 5 mons 12 hours
358358
(10 rows)
359359

360+
-- multiplication and division overflow test cases
361+
SELECT '3000000 months'::interval * 1000;
362+
ERROR: interval out of range
363+
SELECT '3000000 months'::interval / 0.001;
364+
ERROR: interval out of range
365+
SELECT '3000000 days'::interval * 1000;
366+
ERROR: interval out of range
367+
SELECT '3000000 days'::interval / 0.001;
368+
ERROR: interval out of range
369+
SELECT '1 month 2146410 days'::interval * 1000.5002;
370+
ERROR: interval out of range
371+
SELECT make_interval(0, 0, 0, 0, 0, 0, 4611686018427.387904) / 0.1;
372+
ERROR: interval out of range
360373
-- test avg(interval), which is somewhat fragile since people have been
361374
-- known to change the allowed input syntax for type interval without
362375
-- updating pg_aggregate.agginitval

src/test/regress/sql/interval.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@ SET IntervalStyle to postgres_verbose;
129129

130130
SELECT '' AS ten, * FROM INTERVAL_TBL;
131131

132+
-- multiplication and division overflow test cases
133+
SELECT '3000000 months'::interval * 1000;
134+
SELECT '3000000 months'::interval / 0.001;
135+
SELECT '3000000 days'::interval * 1000;
136+
SELECT '3000000 days'::interval / 0.001;
137+
SELECT '1 month 2146410 days'::interval * 1000.5002;
138+
SELECT make_interval(0, 0, 0, 0, 0, 0, 4611686018427.387904) / 0.1;
139+
132140
-- test avg(interval), which is somewhat fragile since people have been
133141
-- known to change the allowed input syntax for type interval without
134142
-- updating pg_aggregate.agginitval

0 commit comments

Comments
 (0)