diff --git a/src/common/backend/utils/adt/arrayfuncs.cpp b/src/common/backend/utils/adt/arrayfuncs.cpp index 6e6ceb3cd..d6bcdf3e3 100644 --- a/src/common/backend/utils/adt/arrayfuncs.cpp +++ b/src/common/backend/utils/adt/arrayfuncs.cpp @@ -18,6 +18,7 @@ #include #include "catalog/pg_proc.h" +#include "common/int.h" #include "funcapi.h" #include "libpq/pqformat.h" #include "utils/array.h" @@ -3008,19 +3009,32 @@ ArrayType* array_set(ArrayType* array, int nSubscripts, const int* indx, Datum d addedbefore = addedafter = 0; /* - * Check subscripts + * Check subscripts. We assume the existing subscripts passed + * ArrayCheckBounds, so that dim[i] + lb[i] can be computed without + * overflow. But we must beware of other overflows in our calculations of + * new dim[] values. */ if (ndim == 1) { if (indx[0] < lb[0]) { - addedbefore = lb[0] - indx[0]; - dim[0] += addedbefore; + if (pg_sub_s32_overflow(lb[0], indx[0], &addedbefore) || + pg_add_s32_overflow(dim[0], addedbefore, &dim[0])) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxArraySize))); + lb[0] = indx[0]; if (addedbefore > 1) newhasnulls = true; /* will insert nulls */ } if (indx[0] >= (dim[0] + lb[0])) { - addedafter = indx[0] - (dim[0] + lb[0]) + 1; - dim[0] += addedafter; + if (pg_sub_s32_overflow(indx[0], dim[0] + lb[0], &addedafter) || + pg_add_s32_overflow(addedafter, 1, &addedafter) || + pg_add_s32_overflow(dim[0], addedafter, &dim[0])) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxArraySize))); if (addedafter > 1) newhasnulls = true; /* will insert nulls */ } @@ -3247,7 +3261,10 @@ ArrayType* array_set_slice(ArrayType* array, int nSubscripts, int* upperIndx, in addedbefore = addedafter = 0; /* - * Check subscripts + * Check subscripts. We assume the existing subscripts passed + * ArrayCheckBounds, so that dim[i] + lb[i] can be computed without + * overflow. But we must beware of other overflows in our calculations of + * new dim[] values. */ if (ndim == 1) { Assert(nSubscripts == 1); @@ -3255,17 +3272,26 @@ ArrayType* array_set_slice(ArrayType* array, int nSubscripts, int* upperIndx, in ereport( ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("upper bound cannot be less than lower bound"))); if (lowerIndx[0] < lb[0]) { - if (upperIndx[0] < lb[0] - 1) - newhasnulls = true; /* will insert nulls */ - addedbefore = lb[0] - lowerIndx[0]; - dim[0] += addedbefore; + if (pg_sub_s32_overflow(lb[0], lowerIndx[0], &addedbefore) || + pg_add_s32_overflow(dim[0], addedbefore, &dim[0])) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxArraySize))); lb[0] = lowerIndx[0]; + if (addedbefore > 1) + newhasnulls = true; /*will insert nulls*/ } if (upperIndx[0] >= (dim[0] + lb[0])) { - if (lowerIndx[0] > (dim[0] + lb[0])) + if (pg_sub_s32_overflow(upperIndx[0], dim[0] + lb[0], &addedafter) || + pg_add_s32_overflow(addedafter, 1, &addedafter) || + pg_add_s32_overflow(dim[0], addedafter, &dim[0])) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxArraySize))); + if (addedafter > 1) newhasnulls = true; /* will insert nulls */ - addedafter = upperIndx[0] - (dim[0] + lb[0]) + 1; - dim[0] += addedafter; } } else { /* diff --git a/src/common/backend/utils/adt/arrayutils.cpp b/src/common/backend/utils/adt/arrayutils.cpp index b4dcfef09..8076eb89f 100644 --- a/src/common/backend/utils/adt/arrayutils.cpp +++ b/src/common/backend/utils/adt/arrayutils.cpp @@ -58,10 +58,6 @@ int ArrayGetOffset0(int n, const int* tup, const int* scale) * This must do overflow checking, since it is used to validate that a user * dimensionality request doesn't overflow what we can handle. * - * We limit array sizes to at most about a quarter billion elements, - * so that it's not necessary to check for overflow in quite so many - * places --- for instance when palloc'ing Datum arrays. - * * The multiplication overflow check only works on machines that have int64 * arithmetic, but that is nearly all platforms these days, and doing check * divides for those that don't seems way too expensive. @@ -71,7 +67,6 @@ int ArrayGetNItems(int ndim, const int* dims) int32 ret; int i; -#define MaxArraySize ((Size)(MaxAllocSize / sizeof(Datum))) if (ndim <= 0) return 0; diff --git a/src/include/utils/array.h b/src/include/utils/array.h index a04491daf..f1ffcba9d 100644 --- a/src/include/utils/array.h +++ b/src/include/utils/array.h @@ -58,6 +58,13 @@ #include "fmgr.h" +/* + * Maximum number of elements in an array. We limit this to at most about a + * quarter billion elements, so that it's not necessary to check for overflow + * in quite so many places --- for instance when polloc'ing Datum arrays. + */ +#define MaxArraySize ((Size)(MaxAllocSize) / sizeof(Datum)) + /* * Arrays are varlena objects, so must meet the varlena convention that * the first int32 of the object contains the total object size in bytes. diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 45a2dcda2..12f00e46f 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -1028,6 +1028,43 @@ select * from arr_tbl where f1 >= '{1,2,3}' and f1 < '{1,5,3}' ORDER BY 1; -- then you didn't get an indexscan plan, and something is busted. reset enable_seqscan; reset enable_bitmapscan; +-- test subscript overflow detection +-- The normal error message includes a platform-dependent limit, +-- so suppress it to avoid needing multiple expected-files. +\set VERBOSITY sqlstate +create temp table arr_pk_tbl (pk int4 primary key, f1 int[]); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "arr_pk_tbl_pkey" for table "arr_pk_tbl" +insert into arr_pk_tbl values(10, '[-2147483648:-2147483647]={1,2}'); +update arr_pk_tbl set f1[2147483647] = 42 where pk = 10; +ERROR: array size exceeds the maximum allowed (134217727) +CONTEXT: referenced column: f1 +select * from arr_pk_tbl; + pk | f1 +----+--------------------------------- + 10 | [-2147483648:-2147483647]={1,2} +(1 row) + +update arr_pk_tbl set f1[-2147483644] = 42 where pk = 10; +select * from arr_pk_tbl; + pk | f1 +----+---------------------------------------------- + 10 | [-2147483648:-2147483644]={1,2,NULL,NULL,42} +(1 row) + +update arr_pk_tbl set f1[2147483646:2147483647] = array[4,2] where pk = 10; +ERROR: array size exceeds the maximum allowed (134217727) +CONTEXT: referenced column: f1 +drop table arr_pk_tbl; +-- also exercise the expanded-array case +do $$ declare a int[]; +begin + a := '[-2147483648:-2147483647]={1,2}'::int[]; + a[2147483647] := 42; + raise info '%', a; +end $$; +ERROR: array size exceeds the maximum allowed (134217727) +CONTEXT: PL/pgSQL function inline_code_block line 4 at assignment +\set VERBOSITY default -- test [not] (like|ilike) (any|all) (...) select 'foo' like any (array['%a', '%o']); -- t ?column? diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index b8dd21057..7d523ed11 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -295,6 +295,30 @@ select * from arr_tbl where f1 >= '{1,2,3}' and f1 < '{1,5,3}' ORDER BY 1; reset enable_seqscan; reset enable_bitmapscan; +-- test subscript overflow detection + +-- The normal error message includes a platform-dependent limit, +-- so suppress it to avoid needing multiple expected-files. +\set VERBOSITY sqlstate +create temp table arr_pk_tbl (pk int4 primary key, f1 int[]); +insert into arr_pk_tbl values(10, '[-2147483648:-2147483647]={1,2}'); +update arr_pk_tbl set f1[2147483647] = 42 where pk = 10; +select * from arr_pk_tbl; +update arr_pk_tbl set f1[-2147483644] = 42 where pk = 10; +select * from arr_pk_tbl; +update arr_pk_tbl set f1[2147483646:2147483647] = array[4,2] where pk = 10; +drop table arr_pk_tbl; + +-- also exercise the expanded-array case +do $$ declare a int[]; +begin + a := '[-2147483648:-2147483647]={1,2}'::int[]; + a[2147483647] := 42; + raise info '%', a; +end $$; + +\set VERBOSITY default + -- test [not] (like|ilike) (any|all) (...) select 'foo' like any (array['%a', '%o']); -- t select 'foo' like any (array['%a', '%b']); -- f