8000 Guard against overflow in interval_mul() and interval_div(). · postgres/postgres@b218fbb · GitHub
[go: up one dir, main page]

Skip to content

Commit b218fbb

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 4bc8f29 commit b218fbb

File tree

3 files changed

+80
-44
lines changed

3 files changed

+80
-44
lines changed

src/backend/utils/adt/timestamp.c

Lines changed: 59 additions & 44 deletions
*/
Original file line numberDiff line numberDiff line change
@@ -3548,17 +3548,14 @@ interval_mul(PG_FUNCTION_ARGS)
35483548
* interval type has nothing equivalent to NaN.
35493549
*/
35503550
if (isnan(factor))
3551-
ereport(ERROR,
3552-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3553-
errmsg("interval out of range")));
3551+
goto out_of_range;
35543552

35553553
if (INTERVAL_NOT_FINITE(span))
35563554
{
35573555
if (factor == 0.0)
3558-
ereport(ERROR,
3559-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3560-
errmsg("interval out of range")));
3561-
else if (factor < 0.0)
3556+
goto out_of_range;
3557+
3558+
if (factor < 0.0)
35623559
interval_um_internal(span, result);
35633560
else
35643561
memcpy(result, span, sizeof(Interval));
@@ -3570,10 +3567,9 @@ interval_mul(PG_FUNCTION_ARGS)
35703567
int isign = interval_sign(span);
35713568

35723569
if (isign == 0)
3573-
ereport(ERROR,
3574-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3575-
errmsg("interval out of range")));
3576-
else if (factor * isign < 0)
3570+
goto out_of_range;
3571+
3572+
if (factor * isign < 0)
35773573
INTERVAL_NOBEGIN(result);
35783574
else
35793575
INTERVAL_NOEND(result);
@@ -3582,19 +3578,13 @@ interval_mul(PG_FUNCTION_ARGS)
35823578
}
35833579

35843580
result_double = span->month * factor;
3585-
if (isnan(result_double) ||
3586-
result_double > INT_MAX || result_double < INT_MIN)
3587-
ereport(ERROR,
3588-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3589-
errmsg("interval out of range")));
3581+
if (isnan(result_double) || !FLOAT8_FITS_IN_INT32(result_double))
3582+
goto out_of_range;
35903583
result->month = (int32) result_double;
35913584

35923585
result_double = span->day * factor;
3593-
if (isnan(result_double) ||
3594-
result_double > INT_MAX || result_double < INT_MIN)
3595-
ereport(ERROR,
3596-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3597-
errmsg("interval out of range")));
3586+
if (isnan(result_double) || !FLOAT8_FITS_IN_INT32(result_double))
3587+
goto out_of_range;
35983588
result->day = (int32) result_double;
35993589

36003590
/*
@@ -3628,25 +3618,33 @@ interval_mul(PG_FUNCTION_ARGS)
36283618
36293619
if (fabs(sec_remainder) >= SECS_PER_DAY)
36303620
{
3631-
result->day += (int) (sec_remainder / SECS_PER_DAY);
3621+
if (pg_add_s32_overflow(result->day,
3622+
(int) (sec_remainder / SECS_PER_DAY),
3623+
&result->day))
3624+
goto out_of_range;
36323625
sec_remainder -= (int) (sec_remainder / SECS_PER_DAY) * SECS_PER_DAY;
36333626
}
36343627

36353628
/* cascade units down */
3636-
result->day += (int32) month_remainder_days;
3629+
if (pg_add_s32_overflow(result->day, (int32) month_remainder_days,
3630+
&result->day))
3631+
goto out_of_range;
36373632
result_double = rint(span->time * factor + sec_remainder * USECS_PER_SEC);
36383633
if (isnan(result_double) || !FLOAT8_FITS_IN_INT64(result_double))
3639-
ereport(ERROR,
3640-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3641-
errmsg("interval out of range")));
3634+
goto out_of_range;
36423635
result->time = (int64) result_double;
36433636

36443637
if (INTERVAL_NOT_FINITE(result))
3645-
ereport(ERROR,
3646-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3647-
errmsg("interval out of range")));
3638+
goto out_of_range;
36483639

36493640
PG_RETURN_INTERVAL_P(result);
3641+
3642+
out_of_range:
3643+
ereport(ERROR,
3644+
errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3645+
errmsg("interval out of range"));
3646+
3647+
PG_RETURN_NULL(); /* keep compiler quiet */
36503648
}
36513649

36523650
Datum
@@ -3665,7 +3663,8 @@ interval_div(PG_FUNCTION_ARGS)
36653663
Interval *span = PG_GETARG_INTERVAL_P(0);
36663664
float8 factor = PG_GETARG_FLOAT8(1);
36673665
double month_remainder_days,
3668-
sec_remainder;
3666+
sec_remainder,
3667+
result_double;
36693668
int32 orig_month = span->month,
36703669
orig_day = span->day;
36713670
Interval *result;
@@ -3685,16 +3684,12 @@ interval_div(PG_FUNCTION_ARGS)
36853684
* by the regular division code, causing all fields to be set to zero.
36863685
*/
36873686
if (isnan(factor))
3688-
ereport(ERROR,
3689-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3690-
errmsg("interval out of range")));
3687+
goto out_of_range;
36913688

36923689
if (INTERVAL_NOT_FINITE(span))
36933690
{
36943691
if (isinf(factor))
3695-
ereport(ERROR,
3696-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3697-
errmsg("interval out of range")));
3692+
goto out_of_range;
36983693

36993694
if (factor < 0.0)
37003695
interval_um_internal(span, result);
@@ -3704,8 +3699,15 @@ interval_div(PG_FUNCTION_ARGS)
37043699
PG_RETURN_INTERVAL_P(result);
37053700
}
37063701

3707-
result->month = (int32) (span->month / factor);
3708-
result->day = (int32) (span->day / factor);
3702+
result_double = span->month / factor;
3703+
if (isnan(result_double) || !FLOAT8_FITS_IN_INT32(result_double))
3704+
goto out_of_range;
3705+
result->month = (int32) result_double;
3706+
3707+
result_double = span->day / factor;
3708+
if (isnan(result_double) || !FLOAT8_FITS_IN_INT32(result_double))
3709+
goto out_of_range;
3710+
result->day = (int32) result_double;
37093711

37103712
/*
37113713
* Fractional months full days into days. See comment in interval_mul().
@@ -3717,20 +3719,33 @@ interval_div(PG_FUNCTION_ARGS)
37173719
sec_remainder = TSROUND(sec_remainder);
37183720
if (fabs(sec_remainder) >= SECS_PER_DAY)
37193721
{
3720-
result->day += (int) (sec_remainder / SECS_PER_DAY);
3722+
if (pg_add_s32_overflow(result->day,
3723+
(int) (sec_remainder / SECS_PER_DAY),
3724+
&result->day))
3725+
goto out_of_range;
37213726
sec_remainder -= (int) (sec_remainder / SECS_PER_DAY) * SECS_PER_DAY;
37223727
}
37233728

37243729
/* cascade units down */
3725-
result->day += (int32) month_remainder_days;
3726-
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
3730+
if (pg_add_s32_overflow(result->day, (int32) month_remainder_days,
3731+
&result->day))
3732+
goto out_of_range;
3733+
result_double = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
3734+
if (isnan(result_double) || !FLOAT8_FITS_IN_INT64(result_double))
3735+
goto out_of_range;
3736+
result->time = (int64) result_double;
37273737

37283738
if (INTERVAL_NOT_FINITE(result))
3729-
ereport(ERROR,
3730-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3731-
errmsg("interval out of range")));
3739+
goto out_of_range;
37323740

37333741
PG_RETURN_INTERVAL_P(result);
3742+
3743+
out_of_range:
3744+
ereport(ERROR,
3745+
errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3746+
errmsg("interval out of range"));
3747+
3748+
PG_RETURN_NULL(); /* keep compiler quiet */
37343749
}
37353750

37363751

src/test/regress/expected/interval.out

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,19 @@ SELECT * FROM INTERVAL_TBL;
488488
-infinity
489489
(12 rows)
490490

491+
-- multiplication and division overflow test cases
492+
SELECT '3000000 months'::interval * 1000;
493+
ERROR: interval out of range
494+
SELECT '3000000 months'::interval / 0.001;
495+
ERROR: interval out of range
496+
SELECT '3000000 days'::interval * 1000;
497+
ERROR: interval out of range
498+
SELECT '3000000 days'::interval / 0.001;
499+
ERROR: interval out of range
500+
SELECT '1 month 2146410 days'::interval * 1000.5002;
501+
ERROR: interval out of range
502+
SELECT '4611686018427387904 usec'::interval / 0.1;
503+
ERROR: interval out of range
491504
-- test avg(interval), which is somewhat fragile since people have been
492505
-- known to change the allowed input syntax for type interval without
493506
-- updating pg_aggregate.agginitval

src/test/regress/sql/interval.sql

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

155155
SELECT * FROM INTERVAL_TBL;
156156

157+
-- multiplication and division overflow test cases
158+
SELECT '3000000 months'::interval * 1000;
159+
SELECT '3000000 months'::interval / 0.001;
160+
SELECT '3000000 days'::interval * 1000;
161+
SELECT '3000000 days'::interval / 0.001;
162+
SELECT '1 month 2146410 days'::interval * 1000.5002;
163+
SELECT '4611686018427387904 usec'::interval / 0.1;
164+
157165
-- test avg(interval), which is somewhat fragile since people have been
158166
-- known to change the allowed input syntax for type interval without
159167
-- updating pg_aggregate.agginitval

0 commit comments

Comments
 (0)
0