Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 7c98759

Browse files
committed
Fix integer-overflow corner cases in substring() functions.
If the substring start index and length overflow when added together, substring() misbehaved, either throwing a bogus "negative substring length" error on a case that should succeed, or failing to complain that a negative length is negative (and instead returning the whole string, in most cases). Unsurprisingly, the text, bytea, and bit variants of the function all had this issue. Rearrange the logic to ensure that negative lengths are always rejected, and add an overflow check to handle the other case. Also install similar guards into detoast_attr_slice() (nee heap_tuple_untoast_attr_slice()), since it's far from clear that no other code paths leading to that function could pass it values that would overflow. Patch by myself and Pavel Stehule, per bug #16804 from Rafi Shamim. Back-patch to v11. While these bugs are old, the common/int.h infrastructure for overflow-detecting arithmetic didn't exist before commit 4d6ad31, and it doesn't seem like these misbehaviors are bad enough to justify developing a standalone fix for the older branches. Discussion: https://postgr.es/m/16804-f4eeeb6c11ba71d4@postgresql.org
1 parent 9a968e7 commit 7c98759

File tree

7 files changed

+195
-61
lines changed

7 files changed

+195
-61
lines changed

src/backend/access/heap/tuptoaster.c

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "access/tuptoaster.h"
3636
#include "access/xact.h"
3737
#include "catalog/catalog.h"
38+
#include "common/int.h"
3839
#include "common/pg_lzcompress.h"
3940
#include "miscadmin.h"
4041
#include "utils/expandeddatum.h"
@@ -252,6 +253,9 @@ heap_tuple_untoast_attr(struct varlena *attr)
252253
*
253254
* Public entry point to get back part of a toasted value
254255
* from compression or external storage.
256+
*
257+
* sliceoffset is where to start (zero or more)
258+
* If slicelength < 0, return everything beyond sliceoffset
255259
* ----------
256260
*/
257261
struct varlena *
@@ -261,8 +265,21 @@ heap_tuple_untoast_attr_slice(struct varlena *attr,
261265
struct varlena *preslice;
262266
struct varlena *result;
263267
char *attrdata;
268+
int32 slicelimit;
264269
int32 attrsize;
265270

271+
if (sliceoffset < 0)
272+
elog(ERROR, "invalid sliceoffset: %d", sliceoffset);
273+
274+
/*
275+
* Compute slicelimit = offset + length, or -1 if we must fetch all of the
276+
* value. In case of integer overflow, we must fetch all.
277+
*/
278+
if (slicelength < 0)
279+
slicelimit = -1;
280+
else if (pg_add_s32_overflow(sliceoffset, slicelength, &slicelimit))
281+
slicelength = slicelimit = -1;
282+
266283
if (VARATT_IS_EXTERNAL_ONDISK(attr))
267284
{
268285
struct varatt_external toast_pointer;
@@ -303,8 +320,8 @@ heap_tuple_untoast_attr_slice(struct varlena *attr,
303320
struct varlena *tmp = preslice;
304321

305322
/* Decompress enough to encompass the slice and the offset */
306-
if (slicelength > 0 && sliceoffset >= 0)
307-
preslice = toast_decompress_datum_slice(tmp, slicelength + sliceoffset);
323+
if (slicelimit >= 0)
324+
preslice = toast_decompress_datum_slice(tmp, slicelimit);
308325
else
309326
preslice = toast_decompress_datum(tmp);
310327

@@ -330,8 +347,7 @@ heap_tuple_untoast_attr_slice(struct varlena *attr,
330347
sliceoffset = 0;
331348
slicelength = 0;
332349
}
333-
334-
if (((sliceoffset + slicelength) > attrsize) || slicelength < 0)
350+
else if (slicelength < 0 || slicelimit > attrsize)
335351
slicelength = attrsize - sliceoffset;
336352

337353
result = (struct varlena *) palloc(slicelength + VARHDRSZ);
@@ -2086,7 +2102,12 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length)
20862102
length = 0;
20872103
}
20882104

2089-
if (((sliceoffset + length) > attrsize) || length < 0)
2105+
/*
2106+
* Adjust length request if needed. (Note: our sole caller,
2107+
* heap_tuple_untoast_attr_slice, protects us against sliceoffset + length
2108+
* overflowing.)
2109+
*/
2110+
else if (((sliceoffset + length) > attrsize) || length < 0)
20902111
length = attrsize - sliceoffset;
20912112

20922113
result = (struct varlena *) palloc(length + VARHDRSZ);

src/backend/utils/adt/varbit.c

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,7 +1059,7 @@ bitsubstring(VarBit *arg, int32 s, int32 l, bool length_not_specified)
10591059
len,
10601060
ishift,
10611061
i;
1062-
int e,
1062+
int32 e,
10631063
s1,
10641064
e1;
10651065
bits8 *r,
@@ -1072,18 +1072,24 @@ bitsubstring(VarBit *arg, int32 s, int32 l, bool length_not_specified)
10721072
{
10731073
e1 = bitlen + 1;
10741074
}
1075-
else
1075+
else if (l < 0)
1076+
{
1077+
/* SQL99 says to throw an error for E < S, i.e., negative length */
1078+
ereport(ERROR,
1079+
(errcode(ERRCODE_SUBSTRING_ERROR),
1080+
errmsg("negative substring length not allowed")));
1081+
e1 = -1; /* silence stupider compilers */
1082+
}
1083+
else if (pg_add_s32_overflow(s, l, &e))
10761084
{
1077-
e = s + l;
1078-
10791085
/*
1080-
* A negative value for L is the only way for the end position to be
1081-
* before the start. SQL99 says to throw an error.
1086+
* L could be large enough for S + L to overflow, in which case the
1087+
* substring must run to end of string.
10821088
*/
1083-
if (e < s)
1084-
ereport(ERROR,
1085-
(errcode(ERRCODE_SUBSTRING_ERROR),
1086-
errmsg("negative substring length not allowed")));
1089+
e1 = bitlen + 1;
1090+
}
1091+
else
1092+
{
10871093
e1 = Min(e, bitlen + 1);
10881094
}
10891095
if (s1 > bitlen || e1 <= s1)

src/backend/utils/adt/varlena.c

Lines changed: 64 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -839,29 +839,38 @@ text_substring(Datum str, int32 start, int32 length, bool length_not_specified)
839839
int32 S = start; /* start position */
840840
int32 S1; /* adjusted start position */
841841
int32 L1; /* adjusted substring length */
842+
int32 E; /* end position */
843+
844+
/*
845+
* SQL99 says S can be zero or negative, but we still must fetch from the
846+
* start of the string.
847+
*/
848+
S1 = Max(S, 1);
842849

843850
/* life is easy if the encoding max length is 1 */
844851
if (eml == 1)
845852
{
846-
S1 = Max(S, 1);
847-
848853
if (length_not_specified) /* special case - get length to end of
849854
* string */
850855
L1 = -1;
851-
else
856+
else if (length < 0)
857+
{
858+
/* SQL99 says to throw an error for E < S, i.e., negative length */
859+
ereport(ERROR,
860+
(errcode(ERRCODE_SUBSTRING_ERROR),
861+
errmsg("negative substring length not allowed")));
862+
L1 = -1; /* silence stupider compilers */
863+
}
864+
else if (pg_add_s32_overflow(S, length, &E))
852865
{
853-
/* end position */
854-
int E = S + length;
855-
856866
/*
857-
* A negative value for L is the only way for the end position to
858-
* be before the start. SQL99 says to throw an error.
867+
* L could be large enough for S + L to overflow, in which case
868+
* the substring must run to end of string.
859869
*/
860-
if (E < S)
861-
ereport(ERROR,
862-
(errcode(ERRCODE_SUBSTRING_ERROR),
863-
errmsg("negative substring length not allowed")));
864-
870+
L1 = -1;
871+
}
872+
else
873+
{
865874
/*
866875
* A zero or negative value for the end position can happen if the
867876
* start was negative or one. SQL99 says to return a zero-length
@@ -875,8 +884,8 @@ text_substring(Datum str, int32 start, int32 length, bool length_not_specified)
875884

876885
/*
877886
* If the start position is past the end of the string, SQL99 says to
878-
* return a zero-length string -- PG_GETARG_TEXT_P_SLICE() will do
879-
* that for us. Convert to zero-based starting position
887+
* return a zero-length string -- DatumGetTextPSlice() will do that
888+
* for us. We need only convert S1 to zero-based starting position.
880889
*/
881890
return DatumGetTextPSlice(str, S1 - 1, L1);
882891
}
@@ -897,12 +906,6 @@ text_substring(Datum str, int32 start, int32 length, bool length_not_specified)
897906
char *s;
898907
text *ret;
899908

900-
/*
901-
* if S is past the end of the string, the tuple toaster will return a
902-
* zero-length string to us
903-
*/
904-
S1 = Max(S, 1);
905-
906909
/*
907910
* We need to start at position zero because there is no way to know
908911
* in advance which byte offset corresponds to the supplied start
@@ -913,19 +916,24 @@ text_substring(Datum str, int32 start, int32 length, bool length_not_specified)
913916
if (length_not_specified) /* special case - get length to end of
914917
* string */
915918
slice_size = L1 = -1;
916-
else
919+
else if (length < 0)
920+
{
921+
/* SQL99 says to throw an error for E < S, i.e., negative length */
922+
ereport(ERROR,
923+
(errcode(ERRCODE_SUBSTRING_ERROR),
924+
errmsg("negative substring length not allowed")));
925+
slice_size = L1 = -1; /* silence stupider compilers */
926+
}
927+
else if (pg_add_s32_overflow(S, length, &E))
917928
{
918-
int E = S + length;
919-
920929
/*
921-
* A negative value for L is the only way for the end position to
922-
* be before the start. SQL99 says to throw an error.
930+
* L could be large enough for S + L to overflow, in which case
931+
* the substring must run to end of string.
923932
*/
924-
if (E < S)
925-
ereport(ERROR,
926-
(errcode(ERRCODE_SUBSTRING_ERROR),
927-
errmsg("negative substring length not allowed")));
928-
933+
slice_size = L1 = -1;
934+
}
935+
else
936+
{
929937
/*
930938
* A zero or negative value for the end position can happen if the
931939
* start was negative or one. SQL99 says to return a zero-length
@@ -943,8 +951,10 @@ text_substring(Datum str, int32 start, int32 length, bool length_not_specified)
943951
/*
944952
* Total slice size in bytes can't be any longer than the start
945953
* position plus substring length times the encoding max length.
954+
* If that overflows, we can just use -1.
946955
*/
947-
slice_size = (S1 + L1) * eml;
956+
if (pg_mul_s32_overflow(E, eml, &slice_size))
957+
slice_size = -1;
948958
}
949959

950960
/*
@@ -3246,9 +3256,13 @@ bytea_substring(Datum str,
32463256
int L,
32473257
bool length_not_specified)
32483258
{
3249-
int S1; /* adjusted start position */
3250-
int L1; /* adjusted substring length */
3259+
int32 S1; /* adjusted start position */
3260+
int32 L1; /* adjusted substring length */
3261+
int32 E; /* end position */
32513262

3263+
/*
3264+
* The logic here should generally match text_substring().
3265+
*/
32523266
S1 = Max(S, 1);
32533267

32543268
if (length_not_specified)
@@ -3259,20 +3273,24 @@ bytea_substring(Datum str,
32593273
*/
32603274
L1 = -1;
32613275
}
3262-
else
3276+
else if (L < 0)
3277+
{
3278+
/* SQL99 says to throw an error for E < S, i.e., negative length */
3279+
ereport(ERROR,
3280+
(errcode(ERRCODE_SUBSTRING_ERROR),
3281+
errmsg("negative substring length not allowed")));
3282+
L1 = -1; /* silence stupider compilers */
3283+
}
3284+
else if (pg_add_s32_overflow(S, L, &E))
32633285
{
3264-
/* end position */
3265-
int E = S + L;
3266-
32673286
/*
3268-
* A negative value for L is the only way for the end position to be
3269-
* before the start. SQL99 says to throw an error.
3287+
* L could be large enough for S + L to overflow, in which case the
3288+
* substring must run to end of string.
32703289
*/
3271-
if (E < S)
3272-
ereport(ERROR,
3273-
(errcode(ERRCODE_SUBSTRING_ERROR),
3274-
errmsg("negative substring length not allowed")));
3275-
3290+
L1 = -1;
3291+
}
3292+
else
3293+
{
32763294
/*
32773295
* A zero or negative value for the end position can happen if the
32783296
* start was negative or one. SQL99 says to return a zero-length
@@ -3287,7 +3305,7 @@ bytea_substring(Datum str,
32873305
/*
32883306
* If the start position is past the end of the string, SQL99 says to
32893307
* return a zero-length string -- DatumGetByteaPSlice() will do that for
3290-
* us. Convert to zero-based starting position
3308+
* us. We need only convert S1 to zero-based starting position.
32913309
*/
32923310
return DatumGetByteaPSlice(str, S1 - 1, L1);
32933311
}

src/test/regress/expected/bit.out

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,35 @@ SELECT v,
106106
01010101010 | 1010 | 01010 | 101010
107107
(4 rows)
108108

109+
-- test overflow cases
110+
SELECT SUBSTRING('01010101'::bit(8) FROM 2 FOR 2147483646) AS "1010101";
111+
1010101
112+
---------
113+
1010101
114+
(1 row)
115+
116+
SELECT SUBSTRING('01010101'::bit(8) FROM -10 FOR 2147483646) AS "01010101";
117+
01010101
118+
----------
119+
01010101
120+
(1 row)
121+
122+
SELECT SUBSTRING('01010101'::bit(8) FROM -10 FOR -2147483646) AS "error";
123+
ERROR: negative substring length not allowed
124+
SELECT SUBSTRING('01010101'::varbit FROM 2 FOR 2147483646) AS "1010101";
125+
1010101
126+
---------
127+
1010101
128+
(1 row)
129+
130+
SELECT SUBSTRING('01010101'::varbit FROM -10 FOR 2147483646) AS "01010101";
131+
01010101
132+
----------
133+
01010101
134+
(1 row)
135+
136+
SELECT SUBSTRING('01010101'::varbit FROM -10 FOR -2147483646) AS "error";
137+
ERROR: negative substring length not allowed
109138
--- Bit operations
110139
DROP TABLE varbit_table;
111140
CREATE TABLE varbit_table (a BIT VARYING(16), b BIT VARYING(16));

src/test/regress/expected/strings.out

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,21 @@ SELECT SUBSTRING('1234567890' FROM 4 FOR 3) = '456' AS "456";
313313
t
314314
(1 row)
315315

316+
-- test overflow cases
317+
SELECT SUBSTRING('string' FROM 2 FOR 2147483646) AS "tring";
318+
tring
319+
-------
320+
tring
321+
(1 row)
322+
323+
SELECT SUBSTRING('string' FROM -10 FOR 2147483646) AS "string";
324+
string
325+
--------
326+
string
327+
(1 row)
328+
329+
SELECT SUBSTRING('string' FROM -10 FOR -2147483646) AS "error";
330+
ERROR: negative substring length not allowed
316331
-- T581 regular expression substring (with SQL's bizarre regexp syntax)
317332
SELECT SUBSTRING('abcdefg' FROM 'a#"(b_d)#"%' FOR '#') AS "bcd";
318333
bcd
@@ -1873,6 +1888,32 @@ SELECT repeat('Pg', -4);
18731888

18741889
(1 row)
18751890

1891+
SELECT SUBSTRING('1234567890'::bytea FROM 3) "34567890";
1892+
34567890
1893+
----------
1894+
34567890
1895+
(1 row)
1896+
1897+
SELECT SUBSTRING('1234567890'::bytea FROM 4 FOR 3) AS "456";
1898+
456
1899+
-----
1900+
456
1901+
(1 row)
1902+
1903+
SELECT SUBSTRING('string'::bytea FROM 2 FOR 2147483646) AS "tring";
1904+
tring
1905+
-------
1906+
tring
1907+
(1 row)
1908+
1909+
SELECT SUBSTRING('string'::bytea FROM -10 FOR 2147483646) AS "string";
1910+
string
1911+
--------
1912+
string
1913+
(1 row)
1914+
1915+
SELECT SUBSTRING('string'::bytea FROM -10 FOR -2147483646) AS "error";
1916+
ERROR: negative substring length not allowed
18761917
SELECT trim(E'\\000'::bytea from E'\\000Tom\\000'::bytea);
18771918
btrim
18781919
-------

0 commit comments

Comments
 (0)