From af465edce9b34aae98293c6a2bdd061ffedbd438 Mon Sep 17 00:00:00 2001 From: Petr Khartskhaev 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 +#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 #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 #include +#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 #include +#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