Skip to content

Commit 8d38086

Browse files
committed
Fix integer-overflow edge case detection in interval_mul and pgbench.
This patch adopts the overflow check logic introduced by commit cbdb8b4 into two more places. interval_mul() failed to notice if it computed a new microseconds value that was one more than INT64_MAX, and pgbench's double-to-int64 logic had the same sorts of edge-case problems that cbdb8b4 fixed in the core code. To make this easier to get right in future, put the guts of the checks into new macros in c.h, and add commentary about how to use the macros correctly. Back-patch to all supported branches, as we did with the previous fix. Yuya Watari Discussion: https://postgr.es/m/CAJ2pMkbkkFw2hb9Qb1Zj8d06EhWAQXFLy73St4qWv6aX=vqnjw@mail.gmail.com
1 parent 1accf99 commit 8d38086

File tree

6 files changed

+44
-63
lines changed

6 files changed

+44
-63
lines changed

src/backend/utils/adt/float.c

Lines changed: 8 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,15 +1157,8 @@ dtoi4(PG_FUNCTION_ARGS)
11571157
*/
11581158
num = rint(num);
11591159

1160-
/*
1161-
* Range check. We must be careful here that the boundary values are
1162-
* expressed exactly in the float domain. We expect PG_INT32_MIN to be an
1163-
* exact power of 2, so it will be represented exactly; but PG_INT32_MAX
1164-
* isn't, and might get rounded off, so avoid using it.
1165-
*/
1166-
if (num < (float8) PG_INT32_MIN ||
1167-
num >= -((float8) PG_INT32_MIN) ||
1168-
isnan(num))
1160+
/* Range check */
1161+
if (isnan(num) || !FLOAT8_FITS_IN_INT32(num))
11691162
ereport(ERROR,
11701163
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
11711164
errmsg("integer out of range")));
@@ -1189,15 +1182,8 @@ dtoi2(PG_FUNCTION_ARGS)
11891182
*/
11901183
num = rint(num);
11911184

1192-
/*
1193-
* Range check. We must be careful here that the boundary values are
1194-
* expressed exactly in the float domain. We expect PG_INT16_MIN to be an
1195-
* exact power of 2, so it will be represented exactly; but PG_INT16_MAX
1196-
* isn't, and might get rounded off, so avoid using it.
1197-
*/
1198-
if (num < (float8) PG_INT16_MIN ||
1199-
num >= -((float8) PG_INT16_MIN) ||
1200-
isnan(num))
1185+
/* Range check */
1186+
if (isnan(num) || !FLOAT8_FITS_IN_INT16(num))
12011187
ereport(ERROR,
12021188
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
12031189
errmsg("smallint out of range")));
@@ -1245,15 +1231,8 @@ ftoi4(PG_FUNCTION_ARGS)
12451231
*/
12461232
num = rint(num);
12471233

1248-
/*
1249-
* Range check. We must be careful here that the boundary values are
1250-
* expressed exactly in the float domain. We expect PG_INT32_MIN to be an
1251-
* exact power of 2, so it will be represented exactly; but PG_INT32_MAX
1252-
* isn't, and might get rounded off, so avoid using it.
1253-
*/
1254-
if (num < (float4) PG_INT32_MIN ||
1255-
num >= -((float4) PG_INT32_MIN) ||
1256-
isnan(num))
1234+
/* Range check */
1235+
if (isnan(num) || !FLOAT4_FITS_IN_INT32(num))
12571236
ereport(ERROR,
12581237
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
12591238
errmsg("integer out of range")));
@@ -1277,15 +1256,8 @@ ftoi2(PG_FUNCTION_ARGS)
12771256
*/
12781257
num = rint(num);
12791258

1280-
/*
1281-
* Range check. We must be careful here that the boundary values are
1282-
* expressed exactly in the float domain. We expect PG_INT16_MIN to be an
1283-
* exact power of 2, so it will be represented exactly; but PG_INT16_MAX
1284-
* isn't, and might get rounded off, so avoid using it.
1285-
*/
1286-
if (num < (float4) PG_INT16_MIN ||
1287-
num >= -((float4) PG_INT16_MIN) ||
1288-
isnan(num))
1259+
/* Range check */
1260+
if (isnan(num) || !FLOAT4_FITS_IN_INT16(num))
12891261
ereport(ERROR,
12901262
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
12911263
errmsg("smallint out of range")));

src/backend/utils/adt/int8.c

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1351,15 +1351,8 @@ dtoi8(PG_FUNCTION_ARGS)
13511351
*/
13521352
num = rint(num);
13531353

1354-
/*
1355-
* Range check. We must be careful here that the boundary values are
1356-
* expressed exactly in the float domain. We expect PG_INT64_MIN to be an
1357-
* exact power of 2, so it will be represented exactly; but PG_INT64_MAX
1358-
* isn't, and might get rounded off, so avoid using it.
1359-
*/
1360-
if (num < (float8) PG_INT64_MIN ||
1361-
num >= -((float8) PG_INT64_MIN) ||
1362-
isnan(num))
1354+
/* Range check */
1355+
if (isnan(num) || !FLOAT8_FITS_IN_INT64(num))
13631356
ereport(ERROR,
13641357
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
13651358
errmsg("bigint out of range")));
@@ -1393,15 +1386,8 @@ ftoi8(PG_FUNCTION_ARGS)
13931386
*/
13941387
num = rint(num);
13951388

1396-
/*
1397-
* Range check. We must be careful here that the boundary values are
1398-
* expressed exactly in the float domain. We expect PG_INT64_MIN to be an
1399-
* exact power of 2, so it will be represented exactly; but PG_INT64_MAX
1400-
* isn't, and might get rounded off, so avoid using it.
1401-
*/
1402-
if (num < (float4) PG_INT64_MIN ||
1403-
num >= -((float4) PG_INT64_MIN) ||
1404-
isnan(num))
1389+
/* Range check */
1390+
if (isnan(num) || !FLOAT4_FITS_IN_INT64(num))
14051391
ereport(ERROR,
14061392
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
14071393
errmsg("bigint out of range")));

src/backend/utils/adt/timestamp.c

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,6 @@
4444

4545
#define SAMESIGN(a,b) (((a) < 0) == ((b) < 0))
4646

47-
#ifndef INT64_MAX
48-
#define INT64_MAX INT64CONST(0x7FFFFFFFFFFFFFFF)
49-
#endif
50-
51-
#ifndef INT64_MIN
52-
#define INT64_MIN (-INT64CONST(0x7FFFFFFFFFFFFFFF) - 1)
53-
#endif
54-
5547
/* Set at postmaster start */
5648
TimestampTz PgStartTime;
5749

@@ -3417,7 +3409,7 @@ interval_mul(PG_FUNCTION_ARGS)
34173409
result->day += (int32) month_remainder_days;
34183410
#ifdef HAVE_INT64_TIMESTAMP
34193411
result_double = rint(span->time * factor + sec_remainder * USECS_PER_SEC);
3420-
if (result_double > INT64_MAX || result_double < INT64_MIN)
3412+
if (isnan(result_double) || !FLOAT8_FITS_IN_INT64(result_double))
34213413
ereport(ERROR,
34223414
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
34233415
errmsg("interval out of range")));

src/include/c.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,31 @@ typedef NameData *Name;
879879
#define STATIC_IF_INLINE
880880
#endif /* PG_USE_INLINE */
881881

882+
/*
883+
* Macros for range-checking float values before converting to integer.
884+
* We must be careful here that the boundary values are expressed exactly
885+
* in the float domain. PG_INTnn_MIN is an exact power of 2, so it will
886+
* be represented exactly; but PG_INTnn_MAX isn't, and might get rounded
887+
* off, so avoid using that.
888+
* The input must be rounded to an integer beforehand, typically with rint(),
889+
* else we might draw the wrong conclusion about close-to-the-limit values.
890+
* These macros will do the right thing for Inf, but not necessarily for NaN,
891+
* so check isnan(num) first if that's a possibility.
892+
*/
893+
#define FLOAT4_FITS_IN_INT16(num) \
894+
((num) >= (float4) PG_INT16_MIN && (num) < -((float4) PG_INT16_MIN))
895+
#define FLOAT4_FITS_IN_INT32(num) \
896+
((num) >= (float4) PG_INT32_MIN && (num) < -((float4) PG_INT32_MIN))
897+
#define FLOAT4_FITS_IN_INT64(num) \
898+
((num) >= (float4) PG_INT64_MIN && (num) < -((float4) PG_INT64_MIN))
899+
#define FLOAT8_FITS_IN_INT16(num) \
900+
((num) >= (float8) PG_INT16_MIN && (num) < -((float8) PG_INT16_MIN))
901+
#define FLOAT8_FITS_IN_INT32(num) \
902+
((num) >= (float8) PG_INT32_MIN && (num) < -((float8) PG_INT32_MIN))
903+
#define FLOAT8_FITS_IN_INT64(num) \
904+
((num) >= (float8) PG_INT64_MIN && (num) < -((float8) PG_INT64_MIN))
905+
906+
882907
/* ----------------------------------------------------------------
883908
* Section 8: random stuff
884909
* ----------------------------------------------------------------

src/test/regress/expected/interval.out

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years');
232232
ERROR: interval out of range
233233
LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years'...
234234
^
235+
-- Test edge-case overflow detection in interval multiplication
236+
select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
237+
ERROR: interval out of range
235238
SELECT r1.*, r2.*
236239
FROM INTERVAL_TBL_OF r1, INTERVAL_TBL_OF r2
237240
WHERE r1.f1 > r2.f1

src/test/regress/sql/interval.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
7373
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 years');
7474
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years');
7575

76+
-- Test edge-case overflow detection in interval multiplication
77+
select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
78+
7679
SELECT r1.*, r2.*
7780
FROM INTERVAL_TBL_OF r1, INTERVAL_TBL_OF r2
7881
WHERE r1.f1 > r2.f1

0 commit comments

Comments
 (0)