postgresql/SOURCES/postgresql-CVE-2026-6473.patch
2026-06-25 19:21:38 -04:00

1863 lines
55 KiB
Diff

From af465edce9b34aae98293c6a2bdd061ffedbd438 Mon Sep 17 00:00:00 2001
From: Petr Khartskhaev <pkhartsk@redhat.com>
Date: Thu, 4 Jun 2026 17:23:34 +0200
Subject: [PATCH] CVE-2026-6473
---
contrib/hstore_plperl/hstore_plperl.c | 2 +-
contrib/hstore_plpython/hstore_plpython.c | 2 +-
contrib/intarray/_int_bool.c | 60 ++++--
contrib/ltree/ltree_io.c | 19 +-
contrib/ltree/ltxtquery_io.c | 51 ++++-
src/backend/regex/regc_color.c | 17 +-
src/backend/regex/regc_cvec.c | 3 +
src/backend/regex/regc_nfa.c | 11 +
src/backend/regex/regcomp.c | 5 +-
src/backend/regex/rege_dfa.c | 23 +-
src/backend/regex/regexec.c | 9 +-
src/backend/storage/ipc/shmem.c | 37 ----
src/backend/tsearch/wparser_def.c | 24 ++-
src/backend/utils/adt/arrayfuncs.c | 13 +-
src/backend/utils/adt/formatting.c | 6 +-
src/backend/utils/adt/pg_locale.c | 2 +-
src/backend/utils/adt/varlena.c | 4 +-
src/backend/utils/mmgr/mcxt.c | 196 ++++++++++++++---
src/common/fe_memutils.c | 188 ++++++++++++++++
src/common/psprintf.c | 3 -
src/common/unicode_norm.c | 14 +-
src/include/common/fe_memutils.h | 40 +++-
src/include/common/int.h | 247 ++++++++++++++++++++++
src/include/regex/regcustom.h | 26 ++-
src/include/regex/regguts.h | 13 ++
src/include/storage/shmem.h | 2 -
src/include/utils/memutils.h | 1 +
src/include/utils/palloc.h | 22 +-
src/interfaces/libpq/fe-exec.c | 29 ++-
src/interfaces/libpq/fe-print.c | 20 +-
src/interfaces/libpq/fe-protocol3.c | 10 +-
src/test/regress/expected/tsearch.out | 10 +
src/test/regress/sql/tsearch.sql | 8 +
33 files changed, 965 insertions(+), 152 deletions(-)
diff --git a/contrib/hstore_plperl/hstore_plperl.c b/contrib/hstore_plperl/hstore_plperl.c
index 1316b05..07ab4bd 100644
--- a/contrib/hstore_plperl/hstore_plperl.c
+++ b/contrib/hstore_plperl/hstore_plperl.c
@@ -121,7 +121,7 @@ plperl_to_hstore(PG_FUNCTION_ARGS)
pcount = hv_iterinit(hv);
- pairs = palloc(pcount * sizeof(Pairs));
+ pairs = palloc_array(Pairs, pcount);
i = 0;
while ((he = hv_iternext(hv)))
diff --git a/contrib/hstore_plpython/hstore_plpython.c b/contrib/hstore_plpython/hstore_plpython.c
index 65ab205..14f74c1 100644
--- a/contrib/hstore_plpython/hstore_plpython.c
+++ b/contrib/hstore_plpython/hstore_plpython.c
@@ -153,7 +153,7 @@ plpython_to_hstore(PG_FUNCTION_ARGS)
Py_ssize_t i;
Pairs *pairs;
- pairs = palloc(pcount * sizeof(*pairs));
+ pairs = palloc_array(Pairs, pcount);
for (i = 0; i < pcount; i++)
{
diff --git a/contrib/intarray/_int_bool.c b/contrib/intarray/_int_bool.c
index 91e2a80..4e54171 100644
--- a/contrib/intarray/_int_bool.c
+++ b/contrib/intarray/_int_bool.c
@@ -437,35 +437,66 @@ boolop(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(result);
}
+/*
+ * Recursively fill the "left" fields of an ITEM array that represents
+ * a valid postfix tree.
+ *
+ * ptr: starting element of array
+ * pos: in/out argument, the array index this call is responsible to fill
+ *
+ * At exit, *pos has been decremented to point before the sub-tree whose
+ * top is the entry-time value of *pos.
+ */
static void
findoprnd(ITEM *ptr, int32 *pos)
{
+ int32 mypos;
+
/* since this function recurses, it could be driven to stack overflow. */
check_stack_depth();
+ /* get the position this call is supposed to update */
+ mypos = *pos;
+ Assert(mypos >= 0);
+
+ /* in all cases, we should decrement *pos to advance over this item */
+ (*pos)--;
+
#ifdef BS_DEBUG
- elog(DEBUG3, (ptr[*pos].type == OPR) ?
- "%d %c" : "%d %d", *pos, ptr[*pos].val);
+ elog(DEBUG3, (ptr[mypos].type == OPR) ?
+ "%d %c" : "%d %d", mypos, ptr[mypos].val);
#endif
- if (ptr[*pos].type == VAL)
+
+ if (ptr[mypos].type == VAL)
{
- ptr[*pos].left = 0;
- (*pos)--;
+ /* base case: a VAL has no operand, so just set its left to zero */
+ ptr[mypos].left = 0;
}
- else if (ptr[*pos].val == (int32) '!')
+ else if (ptr[mypos].val == (int32) '!')
{
- ptr[*pos].left = -1;
- (*pos)--;
+ /* unary operator, likewise easy: operand is just before it */
+ ptr[mypos].left = -1;
+ /* recurse to scan operand */
findoprnd(ptr, pos);
}
else
{
- ITEM *curitem = &ptr[*pos];
- int32 tmp = *pos;
+ /* binary operator */
+ int32 delta;
- (*pos)--;
+ /* recurse to scan right operand */
findoprnd(ptr, pos);
- curitem->left = *pos - tmp;
+ /* we must fill left with offset to left operand's top */
+ /* abs(delta) < QUERYTYPEMAXITEMS, so it can't overflow ... */
+ delta = *pos - mypos;
+ /* ... but it might be too large to fit in the 16-bit left field */
+ Assert(delta < 0);
+ if (unlikely(delta < PG_INT16_MIN))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("query_int expression is too complex")));
+ ptr[mypos].left = (int16) delta;
+ /* recurse to scan left operand */
findoprnd(ptr, pos);
}
}
@@ -515,6 +546,7 @@ bqarr_in(PG_FUNCTION_ARGS)
query->size = state.num;
ptr = GETQUERY(query);
+ /* fill the query array from the data makepol constructed */
for (i = state.num - 1; i >= 0; i--)
{
ptr[i].type = state.str->type;
@@ -524,8 +556,12 @@ bqarr_in(PG_FUNCTION_ARGS)
state.str = tmp;
}
+ /* now fill the "left" fields */
pos = query->size - 1;
findoprnd(ptr, &pos);
+ /* if successful, findoprnd should have scanned the whole array */
+ Assert(pos == -1);
+
#ifdef BS_DEBUG
initStringInfo(&pbuf);
for (i = 0; i < query->size; i++)
diff --git a/contrib/ltree/ltree_io.c b/contrib/ltree/ltree_io.c
index 0a7d9aa..d44044c 100644
--- a/contrib/ltree/ltree_io.c
+++ b/contrib/ltree/ltree_io.c
@@ -7,6 +7,7 @@
#include <ctype.h>
+#include "common/int.h"
#include "ltree.h"
#include "utils/memutils.h"
#include "crc32.h"
@@ -268,7 +269,12 @@ lquery_in(PG_FUNCTION_ARGS)
lptr++;
lptr->start = ptr;
state = LQPRS_WAITDELIM;
- curqlevel->numvar++;
+ if (pg_add_u16_overflow(curqlevel->numvar, 1, &curqlevel->numvar))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("lquery level has too many variants"),
+ errdetail("Number of variants exceeds the maximum allowed (%d).",
+ PG_UINT16_MAX)));
}
else
UNCHAR;
@@ -512,7 +518,16 @@ lquery_in(PG_FUNCTION_ARGS)
lptr = GETVAR(curqlevel);
while (lptr - GETVAR(curqlevel) < curqlevel->numvar)
{
- cur->totallen += MAXALIGN(LVAR_HDRSIZE + lptr->len);
+ int newlen = cur->totallen + MAXALIGN(LVAR_HDRSIZE + lptr->len);
+
+ if (newlen > PG_UINT16_MAX)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("lquery level is too large"),
+ errdetail("Total size of level exceeds the maximum allowed (%d bytes).",
+ PG_UINT16_MAX)));
+ cur->totallen = (uint16) newlen;
+
lrptr->len = lptr->len;
lrptr->flag = lptr->flag;
lrptr->val = ltree_crc32_sz(lptr->start, lptr->len);
diff --git a/contrib/ltree/ltxtquery_io.c b/contrib/ltree/ltxtquery_io.c
index 56bf39d..f1f25c2 100644
--- a/contrib/ltree/ltxtquery_io.c
+++ b/contrib/ltree/ltxtquery_io.c
@@ -274,31 +274,60 @@ makepol(QPRS_STATE *state)
return END;
}
+/*
+ * Recursively fill the "left" fields of an ITEM array that represents
+ * a valid postfix tree.
+ *
+ * ptr: starting element of array
+ * pos: in/out argument, the array index this call is responsible to fill
+ *
+ * At exit, *pos has been incremented to point after the sub-tree whose
+ * top is the entry-time value of *pos.
+ */
static void
findoprnd(ITEM *ptr, int32 *pos)
{
+ int32 mypos;
+
/* since this function recurses, it could be driven to stack overflow. */
check_stack_depth();
- if (ptr[*pos].type == VAL || ptr[*pos].type == VALTRUE)
+ /* get the position this call is supposed to update */
+ mypos = *pos;
+
+ /* in all cases, we should increment *pos to advance over this item */
+ (*pos)++;
+
+ if (ptr[mypos].type == VAL || ptr[mypos].type == VALTRUE)
{
- ptr[*pos].left = 0;
- (*pos)++;
+ /* base case: a VAL has no operand, so just set its left to zero */
+ ptr[mypos].left = 0;
}
- else if (ptr[*pos].val == (int32) '!')
+ else if (ptr[mypos].val == (int32) '!')
{
- ptr[*pos].left = 1;
- (*pos)++;
+ /* unary operator, likewise easy: operand is just after it */
+ ptr[mypos].left = 1;
+ /* recurse to scan operand */
findoprnd(ptr, pos);
}
else
{
- ITEM *curitem = &ptr[*pos];
- int32 tmp = *pos;
+ /* binary operator */
+ int32 delta;
- (*pos)++;
+ /* recurse to scan right operand */
findoprnd(ptr, pos);
- curitem->left = *pos - tmp;
+ /* we must fill left with offset to left operand's top */
+ /* delta can't overflow, see LTXTQUERY_TOO_BIG ... */
+ delta = *pos - mypos;
+ /* ... but it might be too large to fit in the 16-bit left field */
+ Assert(delta > 0);
+ if (unlikely(delta > PG_INT16_MAX))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("ltxtquery is too large")));
+ ptr[mypos].left = (int16) delta;
+ /* recurse to scan left operand */
findoprnd(ptr, pos);
}
}
@@ -375,6 +404,8 @@ queryin(char *buf)
/* set left operand's position for every operator */
pos = 0;
findoprnd(ptr, &pos);
+ /* if successful, findoprnd should have scanned the whole array */
+ Assert(pos == state.num);
return query;
}
diff --git a/src/backend/regex/regc_color.c b/src/backend/regex/regc_color.c
index f5a4151..70e7e28 100644
--- a/src/backend/regex/regc_color.c
+++ b/src/backend/regex/regc_color.c
@@ -218,6 +218,7 @@ newcolor(struct colormap *cm)
n = cm->ncds * 2;
if (n > MAX_COLOR + 1)
n = MAX_COLOR + 1;
+ /* the MAX_COLOR+1 limit ensures these alloc sizes can't overflow: */
if (cm->cd == cm->cdspace)
{
newCd = (struct colordesc *) MALLOC(n * sizeof(struct colordesc));
@@ -434,9 +435,8 @@ newhicolorrow(struct colormap *cm,
CERR(REG_ESPACE);
return 0;
}
- newarray = (color *) REALLOC(cm->hicolormap,
- cm->maxarrayrows * 2 *
- cm->hiarraycols * sizeof(color));
+ newarray = REALLOC_ARRAY(cm->hicolormap, color,
+ cm->maxarrayrows * 2 * cm->hiarraycols);
if (newarray == NULL)
{
CERR(REG_ESPACE);
@@ -477,9 +477,8 @@ newhicolorcols(struct colormap *cm)
CERR(REG_ESPACE);
return;
}
- newarray = (color *) REALLOC(cm->hicolormap,
- cm->maxarrayrows *
- cm->hiarraycols * 2 * sizeof(color));
+ newarray = REALLOC_ARRAY(cm->hicolormap, color,
+ cm->maxarrayrows * cm->hiarraycols * 2);
if (newarray == NULL)
{
CERR(REG_ESPACE);
@@ -652,8 +651,7 @@ subcoloronechr(struct vars *v,
* Potentially, we could need two more colormapranges than we have now, if
* the given chr is in the middle of some existing range.
*/
- newranges = (colormaprange *)
- MALLOC((cm->numcmranges + 2) * sizeof(colormaprange));
+ newranges = MALLOC_ARRAY(colormaprange, cm->numcmranges + 2);
if (newranges == NULL)
{
CERR(REG_ESPACE);
@@ -766,8 +764,7 @@ subcoloronerange(struct vars *v,
* Potentially, if we have N non-adjacent ranges, we could need as many as
* 2N+1 result ranges (consider case where new range spans 'em all).
*/
- newranges = (colormaprange *)
- MALLOC((cm->numcmranges * 2 + 1) * sizeof(colormaprange));
+ newranges = MALLOC_ARRAY(colormaprange, cm->numcmranges * 2 + 1);
if (newranges == NULL)
{
CERR(REG_ESPACE);
diff --git a/src/backend/regex/regc_cvec.c b/src/backend/regex/regc_cvec.c
index 1030621..8dbcf3c 100644
--- a/src/backend/regex/regc_cvec.c
+++ b/src/backend/regex/regc_cvec.c
@@ -40,6 +40,9 @@
/*
* newcvec - allocate a new cvec
+ *
+ * Note: in current usage, nchrs and nranges are never so large that we risk
+ * integer overflow in these size calculations, even with 32-bit size_t.
*/
static struct cvec *
newcvec(int nchrs, /* to hold this many chrs... */
diff --git a/src/backend/regex/regc_nfa.c b/src/backend/regex/regc_nfa.c
index 92c9c4d..ebea789 100644
--- a/src/backend/regex/regc_nfa.c
+++ b/src/backend/regex/regc_nfa.c
@@ -2846,6 +2846,10 @@ compact(struct nfa *nfa,
assert(!NISERR());
+ /*
+ * The REG_MAX_COMPILE_SPACE restriction ensures that integer overflow
+ * can't occur in this loop nor in the allocation requests below.
+ */
nstates = 0;
narcs = 0;
for (s = nfa->states; s != NULL; s = s->next)
@@ -2895,6 +2899,13 @@ compact(struct nfa *nfa,
break;
case LACON:
assert(s->no != cnfa->pre);
+ assert(a->co >= 0);
+ /* make sure the modified color number will fit */
+ if (a->co > MAX_COLOR - cnfa->ncolors)
+ {
+ NERR(REG_ECOLORS);
+ return;
+ }
ca->co = (color) (cnfa->ncolors + a->co);
ca->to = a->to->no;
ca++;
diff --git a/src/backend/regex/regcomp.c b/src/backend/regex/regcomp.c
index 0de7bec..8daeb4e 100644
--- a/src/backend/regex/regcomp.c
+++ b/src/backend/regex/regcomp.c
@@ -500,6 +500,7 @@ moresubs(struct vars *v,
assert(wanted > 0 && (size_t) wanted >= v->nsubs);
n = (size_t) wanted * 3 / 2 + 1;
+ /* n is bounded by the number of states, so no chance of overflow here */
if (v->subs == v->sub10)
{
p = (struct subre **) MALLOC(n * sizeof(struct subre *));
@@ -1953,8 +1954,8 @@ newlacon(struct vars *v,
else
{
n = v->nlacons;
- newlacons = (struct subre *) REALLOC(v->lacons,
- (n + 1) * sizeof(struct subre));
+ /* better use REALLOC_ARRAY here, as struct subre is big */
+ newlacons = REALLOC_ARRAY(v->lacons, struct subre, n + 1);
}
if (newlacons == NULL)
{
diff --git a/src/backend/regex/rege_dfa.c b/src/backend/regex/rege_dfa.c
index 5695e15..7cce6cb 100644
--- a/src/backend/regex/rege_dfa.c
+++ b/src/backend/regex/rege_dfa.c
@@ -469,20 +469,29 @@ newdfa(struct vars *v,
}
else
{
+ /*
+ * Restrict the ranges of nstates and ncolors enough that the arrays
+ * we allocate here have no more than INT_MAX members. This protects
+ * not only the allocation calculations just below, but later indexing
+ * into these arrays.
+ */
+ if (wordsper >= INT_MAX / (nss + WORK) ||
+ cnfa->ncolors >= INT_MAX / nss)
+ {
+ ERR(REG_ETOOBIG);
+ return NULL;
+ }
d = (struct dfa *) MALLOC(sizeof(struct dfa));
if (d == NULL)
{
ERR(REG_ESPACE);
return NULL;
}
- d->ssets = (struct sset *) MALLOC(nss * sizeof(struct sset));
- d->statesarea = (unsigned *) MALLOC((nss + WORK) * wordsper *
- sizeof(unsigned));
+ d->ssets = MALLOC_ARRAY(struct sset, nss);
+ d->statesarea = MALLOC_ARRAY(unsigned, (nss + WORK) * wordsper);
d->work = &d->statesarea[nss * wordsper];
- d->outsarea = (struct sset **) MALLOC(nss * cnfa->ncolors *
- sizeof(struct sset *));
- d->incarea = (struct arcp *) MALLOC(nss * cnfa->ncolors *
- sizeof(struct arcp));
+ d->outsarea = MALLOC_ARRAY(struct sset *, nss * cnfa->ncolors);
+ d->incarea = MALLOC_ARRAY(struct arcp, nss * cnfa->ncolors);
d->cptsmalloced = 1;
d->mallocarea = (char *) d;
if (d->ssets == NULL || d->statesarea == NULL ||
diff --git a/src/backend/regex/regexec.c b/src/backend/regex/regexec.c
index 460c987..d876aa6 100644
--- a/src/backend/regex/regexec.c
+++ b/src/backend/regex/regexec.c
@@ -220,8 +220,7 @@ pg_regexec(regex_t *re,
if (v->g->nsub + 1 <= LOCALMAT)
v->pmatch = mat;
else
- v->pmatch = (regmatch_t *) MALLOC((v->g->nsub + 1) *
- sizeof(regmatch_t));
+ v->pmatch = MALLOC_ARRAY(regmatch_t, v->g->nsub + 1);
if (v->pmatch == NULL)
return REG_ESPACE;
v->nmatch = v->g->nsub + 1;
@@ -247,6 +246,7 @@ pg_regexec(regex_t *re,
v->subdfas = subdfas;
else
{
+ /* ntree is surely less than the number of states, so this is safe: */
v->subdfas = (struct dfa **) MALLOC(n * sizeof(struct dfa *));
if (v->subdfas == NULL)
{
@@ -261,6 +261,7 @@ pg_regexec(regex_t *re,
n = (size_t) v->g->nlacons;
if (n > 0)
{
+ /* nlacons is surely less than the number of arcs, so this is safe: */
v->ladfas = (struct dfa **) MALLOC(n * sizeof(struct dfa *));
if (v->ladfas == NULL)
{
@@ -1107,7 +1108,7 @@ citerdissect(struct vars *v,
max_matches = t->max;
if (max_matches < min_matches)
max_matches = min_matches;
- endpts = (chr **) MALLOC((max_matches + 1) * sizeof(chr *));
+ endpts = MALLOC_ARRAY(chr *, max_matches + 1);
if (endpts == NULL)
return REG_ESPACE;
endpts[0] = begin;
@@ -1310,7 +1311,7 @@ creviterdissect(struct vars *v,
max_matches = t->max;
if (max_matches < min_matches)
max_matches = min_matches;
- endpts = (chr **) MALLOC((max_matches + 1) * sizeof(chr *));
+ endpts = MALLOC_ARRAY(chr *, max_matches + 1);
if (endpts == NULL)
return REG_ESPACE;
endpts[0] = begin;
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index a1567d3..9e43faf 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -466,40 +466,3 @@ ShmemInitStruct(const char *name, Size size, bool *foundPtr)
return structPtr;
}
-
-
-/*
- * Add two Size values, checking for overflow
- */
-Size
-add_size(Size s1, Size s2)
-{
- Size result;
-
- result = s1 + s2;
- /* We are assuming Size is an unsigned type here... */
- if (result < s1 || result < s2)
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("requested shared memory size overflows size_t")));
- return result;
-}
-
-/*
- * Multiply two Size values, checking for overflow
- */
-Size
-mul_size(Size s1, Size s2)
-{
- Size result;
-
- if (s1 == 0 || s2 == 0)
- return 0;
- result = s1 * s2;
- /* We are assuming Size is an unsigned type here... */
- if (result / s2 != s1)
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("requested shared memory size overflows size_t")));
- return result;
-}
diff --git a/src/backend/tsearch/wparser_def.c b/src/backend/tsearch/wparser_def.c
index f307fd1..69d4e36 100644
--- a/src/backend/tsearch/wparser_def.c
+++ b/src/backend/tsearch/wparser_def.c
@@ -2563,6 +2563,9 @@ prsd_headline(PG_FUNCTION_ARGS)
bool highlightall = false;
int max_cover;
ListCell *l;
+ size_t startsellen;
+ size_t stopsellen;
+ size_t fragdelimlen;
/* Extract configuration option values */
prs->startsel = NULL;
@@ -2648,9 +2651,24 @@ prsd_headline(PG_FUNCTION_ARGS)
prs->fragdelim = pstrdup(" ... ");
/* Caller will need these lengths, too */
- prs->startsellen = strlen(prs->startsel);
- prs->stopsellen = strlen(prs->stopsel);
- prs->fragdelimlen = strlen(prs->fragdelim);
+ startsellen = strlen(prs->startsel);
+ stopsellen = strlen(prs->stopsel);
+ fragdelimlen = strlen(prs->fragdelim);
+ if (startsellen > PG_INT16_MAX)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value for \"%s\" is too long", "StartSel")));
+ if (stopsellen > PG_INT16_MAX)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value for \"%s\" is too long", "StopSel")));
+ if (fragdelimlen > PG_INT16_MAX)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value for \"%s\" is too long", "FragmentDelimiter")));
+ prs->startsellen = startsellen;
+ prs->stopsellen = stopsellen;
+ prs->fragdelimlen = fragdelimlen;
PG_RETURN_POINTER(prs);
}
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 3791677..4f661f6 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -5314,6 +5314,7 @@ accumArrayResultArr(ArrayBuildStateArr *astate,
ndatabytes;
char *data;
int i;
+ int newnitems;
/*
* We disallow accumulating null subarrays. Another plausible definition
@@ -5343,6 +5344,14 @@ accumArrayResultArr(ArrayBuildStateArr *astate,
nitems = ArrayGetNItems(ndims, dims);
ndatabytes = ARR_SIZE(arg) - ARR_DATA_OFFSET(arg);
+ /* Check that the array doesn't grow too large */
+ newnitems = astate->nitems + nitems;
+ if (newnitems > MaxArraySize)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("array size exceeds the maximum allowed (%zu)",
+ MaxArraySize)));
+
if (astate->ndims == 0)
{
/* First input; check/save the dimensionality info */
@@ -5410,8 +5419,6 @@ accumArrayResultArr(ArrayBuildStateArr *astate,
/* Deal with null bitmap if needed */
if (astate->nullbitmap || ARR_HASNULL(arg))
{
- int newnitems = astate->nitems + nitems;
-
if (astate->nullbitmap == NULL)
{
/*
@@ -5437,7 +5444,7 @@ accumArrayResultArr(ArrayBuildStateArr *astate,
nitems);
}
- astate->nitems += nitems;
+ astate->nitems = newnitems;
astate->dims[0] += 1;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 0986c50..6d08fdf 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -3563,7 +3563,7 @@ datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval, Oid collid)
/*
* Allocate workspace for result as C string
*/
- result = palloc((fmt_len * DCH_MAX_ITEM_SIZ) + 1);
+ result = palloc(mul_size(fmt_len, DCH_MAX_ITEM_SIZ) + 1);
*result = '\0';
if (fmt_len > DCH_CACHE_SIZE)
@@ -3574,7 +3574,7 @@ datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval, Oid collid)
*/
incache = false;
- format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
+ format = palloc_array(FormatNode, fmt_len + 1);
parse_format(format, fmt_str, DCH_keywords,
DCH_suff, DCH_index, DCH_TYPE, NULL);
@@ -4243,7 +4243,7 @@ NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree)
* Allocate new memory if format picture is bigger than static cache
* and do not use cache (call parser always)
*/
- format = (FormatNode *) palloc((len + 1) * sizeof(FormatNode));
+ format = palloc_array(FormatNode, len + 1);
*shouldFree = true;
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index bb929c5..6f57541 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -1772,7 +1772,7 @@ icu_to_uchar(UChar **buff_uchar, const char *buff, size_t nbytes)
ereport(ERROR,
(errmsg("%s failed: %s", "ucnv_toUChars", u_errorName(status))));
- *buff_uchar = palloc((len_uchar + 1) * sizeof(**buff_uchar));
+ *buff_uchar = palloc_array(UChar, len_uchar + 1);
status = U_ZERO_ERROR;
len_uchar = ucnv_toUChars(icu_converter, *buff_uchar, len_uchar + 1,
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 7279405..aa23634 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -4422,7 +4422,7 @@ replace_text_regexp(text *src_text, void *regexp,
{
text *ret_text;
regex_t *re = (regex_t *) regexp;
- int src_text_len = VARSIZE_ANY_EXHDR(src_text);
+ size_t src_text_len = VARSIZE_ANY_EXHDR(src_text);
StringInfoData buf;
regmatch_t pmatch[REGEXP_REPLACE_BACKREF_CNT];
pg_wchar *data;
@@ -4435,7 +4435,7 @@ replace_text_regexp(text *src_text, void *regexp,
initStringInfo(&buf);
/* Convert data string to wide characters. */
- data = (pg_wchar *) palloc((src_text_len + 1) * sizeof(pg_wchar));
+ data = (pg_wchar *) palloc_array(pg_wchar, src_text_len + 1);
data_len = pg_mb2wchar_with_len(VARDATA_ANY(src_text), data, src_text_len);
/* Check whether replace_text has escape char. */
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index b07be12..7948c1c 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -21,6 +21,7 @@
#include "postgres.h"
+#include "common/int.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "utils/memdebug.h"
@@ -58,6 +59,8 @@ static void MemoryContextStatsInternal(MemoryContext context, int level,
MemoryContextCounters *totals);
static void MemoryContextStatsPrint(MemoryContext context, void *passthru,
const char *stats_string);
+static pg_noinline void add_size_error(Size s1, Size s2) pg_attribute_noreturn();
+static pg_noinline void mul_size_error(Size s1, Size s2) pg_attribute_noreturn();
/*
* You should not do memory allocations within a critical section, because
@@ -1070,6 +1073,172 @@ repalloc(void *pointer, Size size)
return ret;
}
+/*
+ * repalloc_extended
+ * Adjust the size of a previously allocated chunk,
+ * with HUGE and NO_OOM options.
+ */
+void *
+repalloc_extended(void *pointer, Size size, int flags)
+{
+ MemoryContext context = GetMemoryChunkContext(pointer);
+ void *ret;
+
+ if (!((flags & MCXT_ALLOC_HUGE) != 0 ? AllocHugeSizeIsValid(size) :
+ AllocSizeIsValid(size)))
+ elog(ERROR, "invalid memory alloc request size %zu", size);
+
+ AssertNotInCriticalSection(context);
+
+ /* isReset must be false already */
+ Assert(!context->isReset);
+
+ ret = context->methods->realloc(context, pointer, size);
+ if (unlikely(ret == NULL))
+ {
+ if ((flags & MCXT_ALLOC_NO_OOM) == 0)
+ {
+ MemoryContextStats(TopMemoryContext);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory"),
+ errdetail("Failed on request of size %zu in memory context \"%s\".",
+ size, context->name)));
+ }
+ return NULL;
+ }
+
+ VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
+
+ return ret;
+}
+
+/*
+ * Support for safe calculation of memory request sizes
+ *
+ * These functions perform the requested calculation, but throw error if the
+ * result overflows.
+ *
+ * An important property of these functions is that if an argument was a
+ * negative signed int before promotion (implying overflow in calculating it)
+ * we will detect that as an error. That happens because we reject results
+ * larger than SIZE_MAX / 2 later on, in the actual allocation step.
+ */
+Size
+add_size(Size s1, Size s2)
+{
+ Size result;
+
+ if (unlikely(pg_add_size_overflow(s1, s2, &result)))
+ add_size_error(s1, s2);
+ return result;
+}
+
+static pg_noinline void
+add_size_error(Size s1, Size s2)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("invalid memory allocation request size %zu + %zu",
+ s1, s2)));
+}
+
+Size
+mul_size(Size s1, Size s2)
+{
+ Size result;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &result)))
+ mul_size_error(s1, s2);
+ return result;
+}
+
+static pg_noinline void
+mul_size_error(Size s1, Size s2)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("invalid memory allocation request size %zu * %zu",
+ s1, s2)));
+}
+
+/*
+ * palloc_mul
+ * Equivalent to palloc(mul_size(s1, s2)).
+ */
+void *
+palloc_mul(Size s1, Size s2)
+{
+ /* inline mul_size() for efficiency */
+ Size req;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &req)))
+ mul_size_error(s1, s2);
+ return palloc(req);
+}
+
+/*
+ * palloc0_mul
+ * Equivalent to palloc0(mul_size(s1, s2)).
+ *
+ * This is comparable to standard calloc's behavior.
+ */
+void *
+palloc0_mul(Size s1, Size s2)
+{
+ /* inline mul_size() for efficiency */
+ Size req;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &req)))
+ mul_size_error(s1, s2);
+ return palloc0(req);
+}
+
+/*
+ * palloc_mul_extended
+ * Equivalent to palloc_extended(mul_size(s1, s2), flags).
+ */
+void *
+palloc_mul_extended(Size s1, Size s2, int flags)
+{
+ /* inline mul_size() for efficiency */
+ Size req;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &req)))
+ mul_size_error(s1, s2);
+ return palloc_extended(req, flags);
+}
+
+/*
+ * repalloc_mul
+ * Equivalent to repalloc(p, mul_size(s1, s2)).
+ */
+void *
+repalloc_mul(void *p, Size s1, Size s2)
+{
+ /* inline mul_size() for efficiency */
+ Size req;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &req)))
+ mul_size_error(s1, s2);
+ return repalloc(p, req);
+}
+
+/*
+ * repalloc_mul_extended
+ * Equivalent to repalloc_extended(p, mul_size(s1, s2), flags).
+ */
+void *
+repalloc_mul_extended(void *p, Size s1, Size s2, int flags)
+{
+ /* inline mul_size() for efficiency */
+ Size req;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &req)))
+ mul_size_error(s1, s2);
+ return repalloc_extended(p, req, flags);
+}
+
/*
* MemoryContextAllocHuge
* Allocate (possibly-expansive) space within the specified context.
@@ -1113,31 +1282,8 @@ MemoryContextAllocHuge(MemoryContext context, Size size)
void *
repalloc_huge(void *pointer, Size size)
{
- MemoryContext context = GetMemoryChunkContext(pointer);
- void *ret;
-
- if (!AllocHugeSizeIsValid(size))
- elog(ERROR, "invalid memory alloc request size %zu", size);
-
- AssertNotInCriticalSection(context);
-
- /* isReset must be false already */
- Assert(!context->isReset);
-
- ret = context->methods->realloc(context, pointer, size);
- if (unlikely(ret == NULL))
- {
- MemoryContextStats(TopMemoryContext);
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("out of memory"),
- errdetail("Failed on request of size %zu in memory context \"%s\".",
- size, context->name)));
- }
-
- VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
-
- return ret;
+ /* this one seems not worth its own implementation */
+ return repalloc_extended(pointer, size, MCXT_ALLOC_HUGE);
}
/*
diff --git a/src/common/fe_memutils.c b/src/common/fe_memutils.c
index ce99b4f..d2ec958 100644
--- a/src/common/fe_memutils.c
+++ b/src/common/fe_memutils.c
@@ -19,6 +19,12 @@
#include "postgres_fe.h"
+#include "common/int.h"
+
+static pg_noinline void add_size_error(Size s1, Size s2) pg_attribute_noreturn();
+static pg_noinline void mul_size_error(Size s1, Size s2) pg_attribute_noreturn();
+
+
static inline void *
pg_malloc_internal(size_t size, int flags)
{
@@ -147,3 +153,185 @@ repalloc(void *pointer, Size size)
{
return pg_realloc(pointer, size);
}
+
+/*
+ * Support for safe calculation of memory request sizes
+ *
+ * These functions perform the requested calculation, but throw error if the
+ * result overflows.
+ *
+ * An important property of these functions is that if an argument was a
+ * negative signed int before promotion (implying overflow in calculating it)
+ * we will detect that as an error. That happens because we reject results
+ * larger than SIZE_MAX / 2. In the backend we rely on later checks to do
+ * that, but in frontend we must do it here.
+ */
+Size
+add_size(Size s1, Size s2)
+{
+ Size result;
+
+ if (unlikely(pg_add_size_overflow(s1, s2, &result) ||
+ result > (SIZE_MAX / 2)))
+ add_size_error(s1, s2);
+ return result;
+}
+
+static pg_noinline void
+add_size_error(Size s1, Size s2)
+{
+ fprintf(stderr, _("invalid memory allocation request size %zu + %zu\n"),
+ s1, s2);
+ exit(EXIT_FAILURE);
+}
+
+Size
+mul_size(Size s1, Size s2)
+{
+ Size result;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &result) ||
+ result > (SIZE_MAX / 2)))
+ mul_size_error(s1, s2);
+ return result;
+}
+
+static pg_noinline void
+mul_size_error(Size s1, Size s2)
+{
+ fprintf(stderr, _("invalid memory allocation request size %zu * %zu\n"),
+ s1, s2);
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * pg_malloc_mul
+ * Equivalent to pg_malloc(mul_size(s1, s2)).
+ */
+void *
+pg_malloc_mul(Size s1, Size s2)
+{
+ /* inline mul_size() for efficiency */
+ Size req;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &req) ||
+ req > (SIZE_MAX / 2)))
+ mul_size_error(s1, s2);
+ return pg_malloc(req);
+}
+
+/*
+ * pg_malloc0_mul
+ * Equivalent to pg_malloc0(mul_size(s1, s2)).
+ *
+ * This is comparable to standard calloc's behavior.
+ */
+void *
+pg_malloc0_mul(Size s1, Size s2)
+{
+ /* inline mul_size() for efficiency */
+ Size req;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &req) ||
+ req > (SIZE_MAX / 2)))
+ mul_size_error(s1, s2);
+ return pg_malloc0(req);
+}
+
+/*
+ * pg_malloc_mul_extended
+ * Equivalent to pg_malloc_extended(mul_size(s1, s2), flags).
+ */
+void *
+pg_malloc_mul_extended(Size s1, Size s2, int flags)
+{
+ /* inline mul_size() for efficiency */
+ Size req;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &req) ||
+ req > (SIZE_MAX / 2)))
+ mul_size_error(s1, s2);
+ return pg_malloc_extended(req, flags);
+}
+
+/*
+ * pg_realloc_mul
+ * Equivalent to pg_realloc(p, mul_size(s1, s2)).
+ */
+void *
+pg_realloc_mul(void *p, Size s1, Size s2)
+{
+ /* inline mul_size() for efficiency */
+ Size req;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &req) ||
+ req > (SIZE_MAX / 2)))
+ mul_size_error(s1, s2);
+ return pg_realloc(p, req);
+}
+
+/*
+ * palloc_mul
+ * Equivalent to palloc(mul_size(s1, s2)).
+ */
+void *
+palloc_mul(Size s1, Size s2)
+{
+ /* inline mul_size() for efficiency */
+ Size req;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &req) ||
+ req > (SIZE_MAX / 2)))
+ mul_size_error(s1, s2);
+ return palloc(req);
+}
+
+/*
+ * palloc0_mul
+ * Equivalent to palloc0(mul_size(s1, s2)).
+ *
+ * This is comparable to standard calloc's behavior.
+ */
+void *
+palloc0_mul(Size s1, Size s2)
+{
+ /* inline mul_size() for efficiency */
+ Size req;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &req) ||
+ req > (SIZE_MAX / 2)))
+ mul_size_error(s1, s2);
+ return palloc0(req);
+}
+
+/*
+ * palloc_mul_extended
+ * Equivalent to palloc_extended(mul_size(s1, s2), flags).
+ */
+void *
+palloc_mul_extended(Size s1, Size s2, int flags)
+{
+ /* inline mul_size() for efficiency */
+ Size req;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &req) ||
+ req > (SIZE_MAX / 2)))
+ mul_size_error(s1, s2);
+ return palloc_extended(req, flags);
+}
+
+/*
+ * repalloc_mul
+ * Equivalent to repalloc(p, mul_size(s1, s2)).
+ */
+void *
+repalloc_mul(void *p, Size s1, Size s2)
+{
+ /* inline mul_size() for efficiency */
+ Size req;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &req) ||
+ req > (SIZE_MAX / 2)))
+ mul_size_error(s1, s2);
+ return repalloc(p, req);
+}
diff --git a/src/common/psprintf.c b/src/common/psprintf.c
index e8a7e44..3759e78 100644
--- a/src/common/psprintf.c
+++ b/src/common/psprintf.c
@@ -24,9 +24,6 @@
#include "postgres_fe.h"
-/* It's possible we could use a different value for this in frontend code */
-#define MaxAllocSize ((Size) 0x3fffffff) /* 1 gigabyte - 1 */
-
#endif
diff --git a/src/common/unicode_norm.c b/src/common/unicode_norm.c
index f7c2d1f..8611b5f 100644
--- a/src/common/unicode_norm.c
+++ b/src/common/unicode_norm.c
@@ -14,6 +14,7 @@
*/
#ifndef FRONTEND
#include "postgres.h"
+#include "utils/memutils.h"
#else
#include "postgres_fe.h"
#endif
@@ -325,8 +326,19 @@ unicode_normalize_kc(const pg_wchar *input)
* Calculate how many characters long the decomposed version will be.
*/
decomp_size = 0;
- for (p = input; *p; p++)
+ for (p = input; *p; p++) {
decomp_size += get_decomposed_size(*p);
+ if (unlikely(decomp_size > MaxAllocSize / sizeof(pg_wchar)))
+ {
+#ifndef FRONTEND
+ /* Exit loop and let palloc() throw error below */
+ break;
+#else
+ /* Just return NULL with no explicit error */
+ return NULL;
+#endif
+ }
+ }
decomp_chars = (pg_wchar *) ALLOC((decomp_size + 1) * sizeof(pg_wchar));
if (decomp_chars == NULL)
diff --git a/src/include/common/fe_memutils.h b/src/include/common/fe_memutils.h
index 740c7c1..f5278d3 100644
--- a/src/include/common/fe_memutils.h
+++ b/src/include/common/fe_memutils.h
@@ -9,6 +9,18 @@
#ifndef FE_MEMUTILS_H
#define FE_MEMUTILS_H
+/*
+ * Assumed maximum size for allocation requests.
+ *
+ * We don't enforce this, so the actual maximum is the platform's SIZE_MAX.
+ * But it's useful to have it defined in frontend builds, so that common
+ * code can check for oversized requests without having frontend-vs-backend
+ * differences. Also, some code relies on MaxAllocSize being no more than
+ * INT_MAX/2, so rather than setting this to SIZE_MAX, make it the same as
+ * the backend's value.
+ */
+#define MaxAllocSize ((Size) 0x3fffffff) /* 1 gigabyte - 1 */
+
/*
* Flags for pg_malloc_extended and palloc_extended, deliberately named
* the same as the backend flags.
@@ -29,6 +41,16 @@ extern void *pg_malloc_extended(size_t size, int flags);
extern void *pg_realloc(void *pointer, size_t size);
extern void pg_free(void *pointer);
+/*
+ * Support for safe calculation of memory request sizes
+ */
+extern Size add_size(Size s1, Size s2);
+extern Size mul_size(Size s1, Size s2);
+extern void *pg_malloc_mul(Size s1, Size s2);
+extern void *pg_malloc0_mul(Size s1, Size s2);
+extern void *pg_malloc_mul_extended(Size s1, Size s2, int flags);
+extern void *pg_realloc_mul(void *p, Size s1, Size s2);
+
/*
* Variants with easier notation and more type safety
*/
@@ -42,14 +64,15 @@ extern void pg_free(void *pointer);
/*
* Allocate space for "count" objects of type "type"
*/
-#define pg_malloc_array(type, count) ((type *) pg_malloc(sizeof(type) * (count)))
-#define pg_malloc0_array(type, count) ((type *) pg_malloc0(sizeof(type) * (count)))
+#define pg_malloc_array(type, count) ((type *) pg_malloc_mul(sizeof(type), count))
+#define pg_malloc0_array(type, count) ((type *) pg_malloc0_mul(sizeof(type), count))
+#define pg_malloc_array_extended(type, count, flags) ((type *) pg_malloc_mul_extended(sizeof(type), count, flags))
/*
* Change size of allocation pointed to by "pointer" to have space for "count"
* objects of type "type"
*/
-#define pg_realloc_array(pointer, type, count) ((type *) pg_realloc(pointer, sizeof(type) * (count)))
+#define pg_realloc_array(pointer, type, count) ((type *) pg_realloc_mul(pointer, sizeof(type), count))
/* Equivalent functions, deliberately named the same as backend functions */
extern char *pstrdup(const char *in);
@@ -58,12 +81,17 @@ extern void *palloc0(Size size);
extern void *palloc_extended(Size size, int flags);
extern void *repalloc(void *pointer, Size size);
extern void pfree(void *pointer);
+extern void *palloc_mul(Size s1, Size s2);
+extern void *palloc0_mul(Size s1, Size s2);
+extern void *palloc_mul_extended(Size s1, Size s2, int flags);
+extern void *repalloc_mul(void *p, Size s1, Size s2);
#define palloc_object(type) ((type *) palloc(sizeof(type)))
#define palloc0_object(type) ((type *) palloc0(sizeof(type)))
-#define palloc_array(type, count) ((type *) palloc(sizeof(type) * (count)))
-#define palloc0_array(type, count) ((type *) palloc0(sizeof(type) * (count)))
-#define repalloc_array(pointer, type, count) ((type *) repalloc(pointer, sizeof(type) * (count)))
+#define palloc_array(type, count) ((type *) palloc_mul(sizeof(type), count))
+#define palloc0_array(type, count) ((type *) palloc0_mul(sizeof(type), count))
+#define palloc_array_extended(type, count, flags) ((type *) palloc_mul_extended(sizeof(type), count, flags))
+#define repalloc_array(pointer, type, count) ((type *) repalloc_mul(pointer, sizeof(type), count))
/* sprintf into a palloc'd buffer --- these are in psprintf.c */
extern char *psprintf(const char *fmt,...) pg_attribute_printf(1, 2);
diff --git a/src/include/common/int.h b/src/include/common/int.h
index b3abce6..0d67248 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -274,4 +274,251 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*------------------------------------------------------------------------
+ * Overflow routines for unsigned integers
+ *------------------------------------------------------------------------
+ */
+
+/*
+ * UINT16
+ */
+static inline bool
+pg_add_u16_overflow(uint16 a, uint16 b, uint16 *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+ return __builtin_add_overflow(a, b, result);
+#else
+ uint16 res = a + b;
+
+ if (res < a)
+ {
+ *result = 0x5EED; /* to avoid spurious warnings */
+ return true;
+ }
+ *result = res;
+ return false;
+#endif
+}
+
+static inline bool
+pg_sub_u16_overflow(uint16 a, uint16 b, uint16 *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+ return __builtin_sub_overflow(a, b, result);
+#else
+ if (b > a)
+ {
+ *result = 0x5EED; /* to avoid spurious warnings */
+ return true;
+ }
+ *result = a - b;
+ return false;
+#endif
+}
+
+static inline bool
+pg_mul_u16_overflow(uint16 a, uint16 b, uint16 *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+ return __builtin_mul_overflow(a, b, result);
+#else
+ uint32 res = (uint32) a * (uint32) b;
+
+ if (res > PG_UINT16_MAX)
+ {
+ *result = 0x5EED; /* to avoid spurious warnings */
+ return true;
+ }
+ *result = (uint16) res;
+ return false;
+#endif
+}
+
+/*
+ * INT32
+ */
+static inline bool
+pg_add_u32_overflow(uint32 a, uint32 b, uint32 *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+ return __builtin_add_overflow(a, b, result);
+#else
+ uint32 res = a + b;
+
+ if (res < a)
+ {
+ *result = 0x5EED; /* to avoid spurious warnings */
+ return true;
+ }
+ *result = res;
+ return false;
+#endif
+}
+
+static inline bool
+pg_sub_u32_overflow(uint32 a, uint32 b, uint32 *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+ return __builtin_sub_overflow(a, b, result);
+#else
+ if (b > a)
+ {
+ *result = 0x5EED; /* to avoid spurious warnings */
+ return true;
+ }
+ *result = a - b;
+ return false;
+#endif
+}
+
+static inline bool
+pg_mul_u32_overflow(uint32 a, uint32 b, uint32 *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+ return __builtin_mul_overflow(a, b, result);
+#else
+ uint64 res = (uint64) a * (uint64) b;
+
+ if (res > PG_UINT32_MAX)
+ {
+ *result = 0x5EED; /* to avoid spurious warnings */
+ return true;
+ }
+ *result = (uint32) res;
+ return false;
+#endif
+}
+
+/*
+ * UINT64
+ */
+static inline bool
+pg_add_u64_overflow(uint64 a, uint64 b, uint64 *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+ return __builtin_add_overflow(a, b, result);
+#else
+ uint64 res = a + b;
+
+ if (res < a)
+ {
+ *result = 0x5EED; /* to avoid spurious warnings */
+ return true;
+ }
+ *result = res;
+ return false;
+#endif
+}
+
+static inline bool
+pg_sub_u64_overflow(uint64 a, uint64 b, uint64 *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+ return __builtin_sub_overflow(a, b, result);
+#else
+ if (b > a)
+ {
+ *result = 0x5EED; /* to avoid spurious warnings */
+ return true;
+ }
+ *result = a - b;
+ return false;
+#endif
+}
+
+static inline bool
+pg_mul_u64_overflow(uint64 a, uint64 b, uint64 *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+ return __builtin_mul_overflow(a, b, result);
+#elif defined(HAVE_INT128)
+ uint128 res = (uint128) a * (uint128) b;
+
+ if (res > PG_UINT64_MAX)
+ {
+ *result = 0x5EED; /* to avoid spurious warnings */
+ return true;
+ }
+ *result = (uint64) res;
+ return false;
+#else
+ uint64 res = a * b;
+
+ if (a != 0 && b != res / a)
+ {
+ *result = 0x5EED; /* to avoid spurious warnings */
+ return true;
+ }
+ *result = res;
+ return false;
+#endif
+}
+
+/*
+ * size_t
+ */
+static inline bool
+pg_add_size_overflow(size_t a, size_t b, size_t *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+ return __builtin_add_overflow(a, b, result);
+#else
+ size_t res = a + b;
+
+ if (res < a)
+ {
+ *result = 0x5EED; /* to avoid spurious warnings */
+ return true;
+ }
+ *result = res;
+ return false;
+#endif
+}
+
+static inline bool
+pg_sub_size_overflow(size_t a, size_t b, size_t *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+ return __builtin_sub_overflow(a, b, result);
+#else
+ if (b > a)
+ {
+ *result = 0x5EED; /* to avoid spurious warnings */
+ return true;
+ }
+ *result = a - b;
+ return false;
+#endif
+}
+
+static inline bool
+pg_mul_size_overflow(size_t a, size_t b, size_t *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+ return __builtin_mul_overflow(a, b, result);
+#else
+ size_t res = a * b;
+
+ if (a != 0 && b != res / a)
+ {
+ *result = 0x5EED; /* to avoid spurious warnings */
+ return true;
+ }
+ *result = res;
+ return false;
+#endif
+}
+
+/*
+ * pg_neg_size_overflow is currently omitted, to avoid having to reason about
+ * the portability of SSIZE_MIN/_MAX before a use case exists.
+ */
+/*
+ * static inline bool
+ * pg_neg_size_overflow(size_t a, ssize_t *result)
+ * {
+ * ...
+ * }
+ */
+
#endif /* COMMON_INT_H */
diff --git a/src/include/regex/regcustom.h b/src/include/regex/regcustom.h
index 82c9e2f..eee5eac 100644
--- a/src/include/regex/regcustom.h
+++ b/src/include/regex/regcustom.h
@@ -52,8 +52,8 @@
#include <wctype.h>
#endif
+#include "common/int.h"
#include "mb/pg_wchar.h"
-
#include "miscadmin.h" /* needed by rcancelrequested/rstacktoodeep */
@@ -62,8 +62,32 @@
#define MALLOC(n) malloc(n)
#define FREE(p) free(VS(p))
#define REALLOC(p,n) realloc(VS(p),n)
+#define MALLOC_ARRAY(type, n) \
+ ((type *) pg_regex_malloc_array(sizeof(type), n))
+#define REALLOC_ARRAY(p, type, n) \
+ ((type *) pg_regex_realloc_array(p, sizeof(type), n))
#define assert(x) Assert(x)
+static inline void *
+pg_regex_malloc_array(size_t s1, size_t s2)
+{
+ size_t req;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &req)))
+ return NULL;
+ return malloc(req);
+}
+
+static inline void *
+pg_regex_realloc_array(void *p, size_t s1, size_t s2)
+{
+ size_t req;
+
+ if (unlikely(pg_mul_size_overflow(s1, s2, &req)))
+ return NULL;
+ return realloc(p, req);
+}
+
/* internal character type and related */
typedef pg_wchar chr; /* the type itself */
typedef unsigned uchr; /* unsigned type that will hold a chr */
diff --git a/src/include/regex/regguts.h b/src/include/regex/regguts.h
index 5d0e7a9..ff652e3 100644
--- a/src/include/regex/regguts.h
+++ b/src/include/regex/regguts.h
@@ -76,6 +76,14 @@
#ifndef FREE
#define FREE(p) free(VS(p))
#endif
+#ifndef MALLOC_ARRAY
+/* we don't depend on calloc's zeroing behavior, we do need overflow check */
+#define MALLOC_ARRAY(type, n) ((type *) calloc(sizeof(type), n))
+#endif
+#ifndef REALLOC_ARRAY
+/* XXX this definition does not provide the desired overflow check */
+#define REALLOC_ARRAY(p, type, n) ((type *) REALLOC(p, sizeof(type) * (n)))
+#endif
/* want size of a char in bits, and max value in bounded quantifiers */
#ifndef _POSIX2_RE_DUP_MAX
@@ -378,6 +386,11 @@ struct cnfa
* transient data is generally not large enough to notice compared to those.
* Note that we do not charge anything for the final output data structures
* (the compacted NFA and the colormap).
+ *
+ * Do not raise this so high as to allow more than INT_MAX/8 states or arcs,
+ * or you risk integer overflows in various space allocation requests.
+ * (We could be more defensive in those places, but that's so far beyond the
+ * practical range of NFA sizes that it doesn't seem worth additional code.)
*/
#ifndef REG_MAX_COMPILE_SPACE
#define REG_MAX_COMPILE_SPACE \
diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h
index 876e908..d994669 100644
--- a/src/include/storage/shmem.h
+++ b/src/include/storage/shmem.h
@@ -42,8 +42,6 @@ extern void InitShmemIndex(void);
extern HTAB *ShmemInitHash(const char *name, long init_size, long max_size,
HASHCTL *infoP, int hash_flags);
extern void *ShmemInitStruct(const char *name, Size size, bool *foundPtr);
-extern Size add_size(Size s1, Size s2);
-extern Size mul_size(Size s1, Size s2);
/* ipci.c */
extern void RequestAddinShmemSpace(Size size);
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index ffe6de5..aefe997 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -41,6 +41,7 @@
#define AllocSizeIsValid(size) ((Size) (size) <= MaxAllocSize)
+/* Do not make this any bigger; see add_size() and mul_size() */
#define MaxAllocHugeSize (SIZE_MAX / 2)
#define AllocHugeSizeIsValid(size) ((Size) (size) <= MaxAllocHugeSize)
diff --git a/src/include/utils/palloc.h b/src/include/utils/palloc.h
index 7306739..7d845f9 100644
--- a/src/include/utils/palloc.h
+++ b/src/include/utils/palloc.h
@@ -78,8 +78,22 @@ extern void *palloc(Size size);
extern void *palloc0(Size size);
extern void *palloc_extended(Size size, int flags);
extern void *repalloc(void *pointer, Size size);
+extern void *repalloc_extended(void *pointer,
+ Size size, int flags);
extern void pfree(void *pointer);
+/*
+ * Support for safe calculation of memory request sizes
+ */
+extern Size add_size(Size s1, Size s2);
+extern Size mul_size(Size s1, Size s2);
+extern void *palloc_mul(Size s1, Size s2);
+extern void *palloc0_mul(Size s1, Size s2);
+extern void *palloc_mul_extended(Size s1, Size s2, int flags);
+extern void *repalloc_mul(void *p, Size s1, Size s2);
+extern void *repalloc_mul_extended(void *p, Size s1, Size s2,
+ int flags);
+
/*
* Variants with easier notation and more type safety
*/
@@ -93,14 +107,16 @@ extern void pfree(void *pointer);
/*
* Allocate space for "count" objects of type "type"
*/
-#define palloc_array(type, count) ((type *) palloc(sizeof(type) * (count)))
-#define palloc0_array(type, count) ((type *) palloc0(sizeof(type) * (count)))
+#define palloc_array(type, count) ((type *) palloc_mul(sizeof(type), count))
+#define palloc0_array(type, count) ((type *) palloc0_mul(sizeof(type), count))
+#define palloc_array_extended(type, count, flags) ((type *) palloc_mul_extended(sizeof(type), count, flags))
/*
* Change size of allocation pointed to by "pointer" to have space for "count"
* objects of type "type"
*/
-#define repalloc_array(pointer, type, count) ((type *) repalloc(pointer, sizeof(type) * (count)))
+#define repalloc_array(pointer, type, count) ((type *) repalloc_mul(pointer, sizeof(type), count))
+#define repalloc_array_extended(pointer, type, count, flags) ((type *) repalloc_mul_extended(pointer, sizeof(type), count, flags))
/*
* The result of palloc() is always word-aligned, so we can skip testing
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 5dea7e7..714be29 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <limits.h>
+#include "common/int.h"
#include "libpq-fe.h"
#include "libpq-int.h"
@@ -3551,9 +3551,14 @@ PQescapeInternal(PGconn *conn, const cha
}
/* Allocate output buffer. */
- result_size = input_len + num_quotes + 3; /* two quotes, plus a NUL */
- if (!as_ident && num_backslashes > 0)
- result_size += num_backslashes + 2;
+ if (pg_add_size_overflow(input_len, num_quotes, &result_size) ||
+ pg_add_size_overflow(result_size, 3, &result_size)) /* two quotes plus a NUL */
+ goto overflow;
+ if (!as_ident && num_backslashes > 0) {
+ if (pg_add_size_overflow(result_size, num_backslashes, &result_size) ||
+ pg_add_size_overflow(result_size, 2, &result_size)) /* for " E" prefix */
+ goto overflow;
+ }
result = rp = (char *) malloc(result_size);
if (rp == NULL)
{
@@ -3663,13 +3669,17 @@ PQescapeByteaInternal(PGconn *conn,
for (i = from_length; i > 0; i--, vp++)
{
if (*vp < 0x20 || *vp > 0x7e)
- len += bslash_len + 3;
+ if (pg_add_size_overflow(len, bslash_len + 3, &len)) /* octal "\ooo" */
+ goto overflow;
else if (*vp == '\'')
- len += 2;
+ if (pg_add_size_overflow(len, 2, &len)) /* double each quote */
+ goto overflow;
else if (*vp == '\\')
- len += bslash_len + bslash_len;
+ if (pg_add_size_overflow(len, bslash_len * 2, &len)) /* double each backslash */
+ goto overflow;
else
- len++;
+ if (pg_add_size_overflow(len, 1, &len))
+ goto overflow;
}
}
@@ -3731,6 +3741,11 @@ PQescapeByteaInternal(PGconn *conn,
*rp = '\0';
return result;
+
+overflow:
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("escaped string size exceeds the maximum allowed (%zu)\n"));
+ return NULL;
}
unsigned char *
diff --git a/src/interfaces/libpq/fe-print.c b/src/interfaces/libpq/fe-print.c
index 6b889a9..2382ff2 100644
--- a/src/interfaces/libpq/fe-print.c
+++ b/src/interfaces/libpq/fe-print.c
@@ -33,6 +33,7 @@
#endif
#endif
+#include "common/int.h"
#include "libpq-fe.h"
#include "libpq-int.h"
@@ -476,10 +477,19 @@ do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax,
char *p = NULL;
for (; n < nFields; n++)
- tot += fieldMax[n] + fs_len + (po->standard ? 2 : 0);
+ if (pg_add_size_overflow(tot, fieldMax[n], &tot) ||
+ pg_add_size_overflow(tot, fs_len, &tot) ||
+ (po->standard && pg_add_size_overflow(tot, 2, &tot)))
+ goto overflow;
if (po->standard)
- tot += fs_len * 2 + 2;
- border = malloc(tot + 1);
+ if (pg_add_size_overflow(tot, fs_len, &tot) ||
+ pg_add_size_overflow(tot, fs_len, &tot) ||
+ pg_add_size_overflow(tot, 2, &tot))
+ goto overflow;
+ if (pg_add_size_overflow(tot, 1, &tot)) /* terminator */
+ goto overflow;
+
+ border = malloc(tot);
if (!border)
{
fprintf(stderr, libpq_gettext("out of memory\n"));
@@ -542,6 +552,10 @@ do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax,
else
fprintf(fout, "\n%s\n", border);
return border;
+
+overflow:
+ fprintf(stderr, libpq_gettext("header size exceeds the maximum allowed\n"));
+ return NULL;
}
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 2931453..aa3ec8a 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -17,6 +17,7 @@
#include <ctype.h>
#include <fcntl.h>
+#include "common/int.h"
#include "libpq-fe.h"
#include "libpq-int.h"
@@ -2150,10 +2151,12 @@ build_startup_packet(const PGconn *conn, char *packet,
do { \
if (packet) \
strcpy(packet + packet_len, optname); \
- packet_len += strlen(optname) + 1; \
+ if (pg_add_size_overflow(packet_len, strlen(optname) + 1, &packet_len)) \
+ return 0; \
if (packet) \
strcpy(packet + packet_len, optval); \
- packet_len += strlen(optval) + 1; \
+ if (pg_add_size_overflow(packet_len, strlen(optval) + 1, &packet_len)) \
+ return 0; \
} while(0)
if (conn->pguser && conn->pguser[0])
@@ -2188,7 +2191,8 @@ build_startup_packet(const PGconn *conn, char *packet,
/* Add trailing terminator */
if (packet)
packet[packet_len] = '\0';
- packet_len++;
+ if (pg_add_size_overflow(packet_len, 1, &packet_len))
+ return 0;
return packet_len;
}
diff --git a/src/test/regress/expected/tsearch.out b/src/test/regress/expected/tsearch.out
index 6519afb..ce5ddc5 100644
--- a/src/test/regress/expected/tsearch.out
+++ b/src/test/regress/expected/tsearch.out
@@ -1532,6 +1532,16 @@ NOTICE: text-search query doesn't contain lexemes: ""
foo bar
(1 row)
+-- Test for large values of StartSel, StopSel and FragmentDelimiter
+SELECT ts_headline('english', 'foo barbar', to_tsquery('english', 'foo'),
+ 'StartSel=' || repeat('x', 32768));
+ERROR: value for "StartSel" is too long
+SELECT ts_headline('english', 'foo barbar', to_tsquery('english', 'foo'),
+ 'StopSel=' || repeat('x', 32768));
+ERROR: value for "StopSel" is too long
+SELECT ts_headline('english', 'foo barbar', to_tsquery('english', 'foo'),
+ 'FragmentDelimiter=' || repeat('x', 32768));
+ERROR: value for "FragmentDelimiter" is too long
--Rewrite sub system
CREATE TABLE test_tsquery (txtkeyword TEXT, txtsample TEXT);
\set ECHO none
diff --git a/src/test/regress/sql/tsearch.sql b/src/test/regress/sql/tsearch.sql
index 389c338..5ee5add 100644
--- a/src/test/regress/sql/tsearch.sql
+++ b/src/test/regress/sql/tsearch.sql
@@ -457,6 +457,14 @@ SELECT ts_headline('english',
SELECT ts_headline('english',
'foo bar', to_tsquery('english', ''));
+-- Test for large values of StartSel, StopSel and FragmentDelimiter
+SELECT ts_headline('english', 'foo barbar', to_tsquery('english', 'foo'),
+ 'StartSel=' || repeat('x', 32768));
+SELECT ts_headline('english', 'foo barbar', to_tsquery('english', 'foo'),
+ 'StopSel=' || repeat('x', 32768));
+SELECT ts_headline('english', 'foo barbar', to_tsquery('english', 'foo'),
+ 'FragmentDelimiter=' || repeat('x', 32768));
+
--Rewrite sub system
CREATE TABLE test_tsquery (txtkeyword TEXT, txtsample TEXT);
--
2.54.0