diff --git a/SOURCES/postgresql-CVE-2026-6473.patch b/SOURCES/postgresql-CVE-2026-6473.patch new file mode 100644 index 0000000..e5e29dc --- /dev/null +++ b/SOURCES/postgresql-CVE-2026-6473.patch @@ -0,0 +1,1782 @@ +diff --git a/contrib/hstore_plperl/hstore_plperl.c b/contrib/hstore_plperl/hstore_plperl.c +index 417b721..f4c742e 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 372041d..2d14404 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 4b6a310..d745ee3 100644 +--- a/contrib/intarray/_int_bool.c ++++ b/contrib/intarray/_int_bool.c +@@ -436,35 +436,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); + } + } +@@ -514,6 +545,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; +@@ -523,8 +555,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 15115cb..9031158 100644 +--- a/contrib/ltree/ltree_io.c ++++ b/contrib/ltree/ltree_io.c +@@ -7,6 +7,7 @@ + + #include + ++#include "common/int.h" + #include "crc32.h" + #include "libpq/pqformat.h" + #include "ltree.h" +@@ -338,7 +339,12 @@ parse_lquery(const char *buf) + 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; +@@ -530,7 +536,16 @@ parse_lquery(const char *buf) + 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 d967f92..cf774b4 100644 +--- a/contrib/ltree/ltxtquery_io.c ++++ b/contrib/ltree/ltxtquery_io.c +@@ -271,31 +271,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); + } + } +@@ -372,6 +401,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 3c65d97..8ce89c4 100644 +--- a/src/backend/regex/regc_nfa.c ++++ b/src/backend/regex/regc_nfa.c +@@ -2849,6 +2849,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) +@@ -2898,6 +2902,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 fba4462..f90e727 100644 +--- a/src/backend/regex/regcomp.c ++++ b/src/backend/regex/regcomp.c +@@ -499,6 +499,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 *)); +@@ -1952,8 +1953,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 97716f6..6c9075f 100644 +--- a/src/backend/storage/ipc/shmem.c ++++ b/src/backend/storage/ipc/shmem.c +@@ -491,42 +491,6 @@ ShmemInitStruct(const char *name, Size size, bool *foundPtr) + } + + +-/* +- * 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; +-} +- + /* SQL SRF showing allocated shared memory */ + Datum + pg_get_shmem_allocations(PG_FUNCTION_ARGS) +diff --git a/src/backend/tsearch/wparser_def.c b/src/backend/tsearch/wparser_def.c +index af97b5b..eaa88b0 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 5e9e680..aa3a545 100644 +--- a/src/backend/utils/adt/arrayfuncs.c ++++ b/src/backend/utils/adt/arrayfuncs.c +@@ -5320,6 +5320,7 @@ accumArrayResultArr(ArrayBuildStateArr *astate, + ndatabytes; + char *data; + int i; ++ int newnitems; + + /* + * We disallow accumulating null subarrays. Another plausible definition +@@ -5349,6 +5350,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 */ +@@ -5414,8 +5423,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) + { + /* +@@ -5439,7 +5446,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 47bef08..69382b7 100644 +--- a/src/backend/utils/adt/formatting.c ++++ b/src/backend/utils/adt/formatting.c +@@ -4038,7 +4038,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) +@@ -4049,7 +4049,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_FLAG, NULL); +@@ -4505,7 +4505,7 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, + * Allocate new memory if format picture is bigger than static + * cache and do not use cache (call parser always) + */ +- 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_FLAG | (std ? STD_FLAG : 0), NULL); +@@ -4945,7 +4945,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 3c4fde6..afdc620 100644 +--- a/src/backend/utils/adt/pg_locale.c ++++ b/src/backend/utils/adt/pg_locale.c +@@ -1833,7 +1833,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 9dea2a5..26f86c0 100644 +--- a/src/backend/utils/adt/varlena.c ++++ b/src/backend/utils/adt/varlena.c +@@ -6045,18 +6045,18 @@ unicode_normalize_func(PG_FUNCTION_ARGS) + text *input = PG_GETARG_TEXT_PP(0); + char *formstr = text_to_cstring(PG_GETARG_TEXT_PP(1)); + UnicodeNormalizationForm form; +- int size; ++ size_t size; + pg_wchar *input_chars; + pg_wchar *output_chars; + unsigned char *p; + text *result; +- int i; ++ size_t i; + + form = unicode_norm_form_from_string(formstr); + + /* convert to pg_wchar */ + size = pg_mbstrlen_with_len(VARDATA_ANY(input), VARSIZE_ANY_EXHDR(input)); +- input_chars = palloc((size + 1) * sizeof(pg_wchar)); ++ input_chars = palloc_array(pg_wchar, size + 1); + p = (unsigned char *) VARDATA_ANY(input); + for (i = 0; i < size; i++) + { +@@ -6111,20 +6111,20 @@ unicode_is_normalized(PG_FUNCTION_ARGS) + text *input = PG_GETARG_TEXT_PP(0); + char *formstr = text_to_cstring(PG_GETARG_TEXT_PP(1)); + UnicodeNormalizationForm form; +- int size; ++ size_t size; + pg_wchar *input_chars; + pg_wchar *output_chars; + unsigned char *p; +- int i; ++ size_t i; + UnicodeNormalizationQC quickcheck; +- int output_size; ++ size_t output_size; + bool result; + + form = unicode_norm_form_from_string(formstr); + + /* convert to pg_wchar */ + size = pg_mbstrlen_with_len(VARDATA_ANY(input), VARSIZE_ANY_EXHDR(input)); +- input_chars = palloc((size + 1) * sizeof(pg_wchar)); ++ input_chars = palloc_array(pg_wchar, size + 1); + p = (unsigned char *) VARDATA_ANY(input); + for (i = 0; i < size; i++) + { +diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c +index abda22f..3bb21fc 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 +@@ -1095,6 +1098,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. +@@ -1138,31 +1307,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 b027a02..a902aa6 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) + { +@@ -174,3 +180,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 4d9d15e..1d05fc1 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/stringinfo.c b/src/common/stringinfo.c +index 0badc46..11f5017 100644 +--- a/src/common/stringinfo.c ++++ b/src/common/stringinfo.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 + + #include "lib/stringinfo.h" +diff --git a/src/common/unicode_norm.c b/src/common/unicode_norm.c +index cfea34e..437aed4 100644 +--- a/src/common/unicode_norm.c ++++ b/src/common/unicode_norm.c +@@ -22,6 +22,7 @@ + #include "common/unicode_norm_table.h" + #ifndef FRONTEND + #include "common/unicode_normprops_table.h" ++#include "utils/memutils.h" + #endif + + #ifndef FRONTEND +@@ -330,10 +331,28 @@ unicode_normalize(UnicodeNormalizationForm form, const pg_wchar *input) + + /* + * Calculate how many characters long the decomposed version will be. ++ * ++ * Some characters decompose to quite a few code points, so that the ++ * decomposed version's size could overrun MaxAllocSize, and even 32-bit ++ * size_t, even though the input string presumably fits in that. In ++ * frontend we want to just return NULL in that case, so monitor the sum ++ * and exit early once we'd need more than MaxAllocSize bytes. + */ + decomp_size = 0; + for (p = input; *p; p++) ++ { + decomp_size += get_decomposed_size(*p, compat); ++ 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 85f3c40..5002f89 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); +@@ -59,12 +82,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 4c86265..ea74b48 100644 +--- a/src/include/common/int.h ++++ b/src/include/common/int.h +@@ -438,4 +438,71 @@ pg_mul_u64_overflow(uint64 a, uint64 b, uint64 *result) + #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 100c52d..fd6595b 100644 +--- a/src/include/regex/regcustom.h ++++ b/src/include/regex/regcustom.h +@@ -50,8 +50,8 @@ + #include + #endif + ++#include "common/int.h" + #include "mb/pg_wchar.h" +- + #include "miscadmin.h" /* needed by rcancelrequested/rstacktoodeep */ + + +@@ -60,8 +60,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 e9e32ab..e651c64 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 909bc2e..d2e51ab 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 5983c10..ad238d7 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 076a9d0..8cca8c0 100644 +--- a/src/interfaces/libpq/fe-exec.c ++++ b/src/interfaces/libpq/fe-exec.c +@@ -24,6 +24,7 @@ + #include + #endif + ++#include "common/int.h" + #include "libpq-fe.h" + #include "libpq-int.h" + #include "mb/pg_wchar.h" +@@ -3493,27 +3494,6 @@ PQescapeString(char *to, const char *from, size_t length) + } + + +-/* +- * Frontend version of the backend's add_size(), intended to be API-compatible +- * with the pg_add_*_overflow() helpers. Stores the result into *dst on success. +- * Returns true instead if the addition overflows. +- * +- * TODO: move to common/int.h +- */ +-static bool +-add_size_overflow(size_t s1, size_t s2, size_t *dst) +-{ +- size_t result; +- +- result = s1 + s2; +- if (result < s1 || result < s2) +- return true; +- +- *dst = result; +- return false; +-} +- +- + /* + * Escape arbitrary strings. If as_ident is true, we escape the result + * as an identifier; if false, as a literal. The result is returned in +@@ -3596,14 +3576,14 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident) + * Allocate output buffer. Protect against overflow, in case the caller + * has allocated a large fraction of the available size_t. + */ +- if (add_size_overflow(input_len, num_quotes, &result_size) || +- add_size_overflow(result_size, 3, &result_size)) /* two quotes plus a NUL */ ++ 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 (add_size_overflow(result_size, num_backslashes, &result_size) || +- add_size_overflow(result_size, 2, &result_size)) /* for " E" prefix */ ++ 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; + } + +@@ -3766,9 +3746,9 @@ PQescapeByteaInternal(PGconn *conn, + if (use_hex) + { + /* We prepend "\x" and double each input character. */ +- if (add_size_overflow(len, bslash_len + 1, &len) || +- add_size_overflow(len, from_length, &len) || +- add_size_overflow(len, from_length, &len)) ++ if (pg_add_size_overflow(len, bslash_len + 1, &len) || ++ pg_add_size_overflow(len, from_length, &len) || ++ pg_add_size_overflow(len, from_length, &len)) + goto overflow; + } + else +@@ -3778,22 +3758,22 @@ PQescapeByteaInternal(PGconn *conn, + { + if (*vp < 0x20 || *vp > 0x7e) + { +- if (add_size_overflow(len, bslash_len + 3, &len)) /* octal "\ooo" */ ++ if (pg_add_size_overflow(len, bslash_len + 3, &len)) /* octal "\ooo" */ + goto overflow; + } + else if (*vp == '\'') + { +- if (add_size_overflow(len, 2, &len)) /* double each quote */ ++ if (pg_add_size_overflow(len, 2, &len)) /* double each quote */ + goto overflow; + } + else if (*vp == '\\') + { +- if (add_size_overflow(len, bslash_len * 2, &len)) /* double each backslash */ ++ if (pg_add_size_overflow(len, bslash_len * 2, &len)) /* double each backslash */ + goto overflow; + } + else + { +- if (add_size_overflow(len, 1, &len)) ++ if (pg_add_size_overflow(len, 1, &len)) + goto overflow; + } + } +diff --git a/src/interfaces/libpq/fe-print.c b/src/interfaces/libpq/fe-print.c +index 257533e..372d7dd 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" + +@@ -469,27 +470,6 @@ do_field(const PQprintOpt *po, const PGresult *res, + } + + +-/* +- * Frontend version of the backend's add_size(), intended to be API-compatible +- * with the pg_add_*_overflow() helpers. Stores the result into *dst on success. +- * Returns true instead if the addition overflows. +- * +- * TODO: move to common/int.h +- */ +-static bool +-add_size_overflow(size_t s1, size_t s2, size_t *dst) +-{ +- size_t result; +- +- result = s1 + s2; +- if (result < s1 || result < s2) +- return true; +- +- *dst = result; +- return false; +-} +- +- + static char * + do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax, + const char **fieldNames, unsigned char *fieldNotNum, +@@ -510,20 +490,20 @@ do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax, + for (; n < nFields; n++) + { + /* Field plus separator, plus 2 extra '-' in standard format. */ +- if (add_size_overflow(tot, fieldMax[n], &tot) || +- add_size_overflow(tot, fs_len, &tot) || +- (po->standard && add_size_overflow(tot, 2, &tot))) ++ 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) + { + /* An extra separator at the front and back. */ +- if (add_size_overflow(tot, fs_len, &tot) || +- add_size_overflow(tot, fs_len, &tot) || +- add_size_overflow(tot, 2, &tot)) ++ 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 (add_size_overflow(tot, 1, &tot)) /* terminator */ ++ if (pg_add_size_overflow(tot, 1, &tot)) /* terminator */ + goto overflow; + + border = malloc(tot); +diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c +index a092010..12f9fde 100644 +--- a/src/interfaces/libpq/fe-protocol3.c ++++ b/src/interfaces/libpq/fe-protocol3.c +@@ -27,6 +27,7 @@ + #endif + #endif + ++#include "common/int.h" + #include "libpq-fe.h" + #include "libpq-int.h" + #include "mb/pg_wchar.h" +@@ -2136,26 +2137,6 @@ pqBuildStartupPacket3(PGconn *conn, int *packetlen, + return startpacket; + } + +-/* +- * Frontend version of the backend's add_size(), intended to be API-compatible +- * with the pg_add_*_overflow() helpers. Stores the result into *dst on success. +- * Returns true instead if the addition overflows. +- * +- * TODO: move to common/int.h +- */ +-static bool +-add_size_overflow(size_t s1, size_t s2, size_t *dst) +-{ +- size_t result; +- +- result = s1 + s2; +- if (result < s1 || result < s2) +- return true; +- +- *dst = result; +- return false; +-} +- + /* + * Build a startup packet given a filled-in PGconn structure. + * +@@ -2188,11 +2169,11 @@ build_startup_packet(const PGconn *conn, char *packet, + do { \ + if (packet) \ + strcpy(packet + packet_len, optname); \ +- if (add_size_overflow(packet_len, strlen(optname) + 1, &packet_len)) \ ++ if (pg_add_size_overflow(packet_len, strlen(optname) + 1, &packet_len)) \ + return 0; \ + if (packet) \ + strcpy(packet + packet_len, optval); \ +- if (add_size_overflow(packet_len, strlen(optval) + 1, &packet_len)) \ ++ if (pg_add_size_overflow(packet_len, strlen(optval) + 1, &packet_len)) \ + return 0; \ + } while(0) + +@@ -2228,7 +2209,7 @@ build_startup_packet(const PGconn *conn, char *packet, + /* Add trailing terminator */ + if (packet) + packet[packet_len] = '\0'; +- if (add_size_overflow(packet_len, 1, &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 73f2d13..98c783f 100644 +--- a/src/test/regress/expected/tsearch.out ++++ b/src/test/regress/expected/tsearch.out +@@ -2007,6 +2007,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 a608828..ec2d4e6 100644 +--- a/src/test/regress/sql/tsearch.sql ++++ b/src/test/regress/sql/tsearch.sql +@@ -555,6 +555,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); diff --git a/SOURCES/postgresql-CVE-2026-6475.patch b/SOURCES/postgresql-CVE-2026-6475.patch new file mode 100644 index 0000000..fe9bbbc --- /dev/null +++ b/SOURCES/postgresql-CVE-2026-6475.patch @@ -0,0 +1,155 @@ +From 498829dca45ad207a23954fe2e5a30ed2ef4b363 Mon Sep 17 00:00:00 2001 +From: Michael Paquier +Date: Mon, 11 May 2026 05:13:51 -0700 +Subject: [PATCH] Prevent path traversal in pg_basebackup and pg_rewind + +pg_rewind and pg_basebackup could be fed paths from rogue endpoints that +could overwrite the contents of the client when received, achieving path +traversal. + +There were two areas in the tree that were sensitive to this problem: +- pg_basebackup, through the astreamer code, where no validation was +performed before building an output path when streaming tar data. This +is an issue in v15 and newer versions. +- pg_rewind file operations for paths received through libpq, for all +the stable branches supported. + +In order to address this problem, this commit adds a helper function in +path.c, that reuses path_is_relative_and_below_cwd() after applying +canonicalize_path(). This can be used to validate the paths received +from a connection point. A path is considered invalid if any of the two +following conditions is satisfied: +- The path is absolute. +- The path includes a direct parent-directory reference. + +Reported-by: XlabAI Team of Tencent Xuanwu Lab +Reported-by: Valery Gubanov +Author: Michael Paquier +Reviewed-by: Amit Kapila +Backpatch-through: 14 +Security: CVE-2026-6475 +--- + src/bin/pg_rewind/file_ops.c | 23 +++++++++++++++++++++++ + src/include/port.h | 1 + + src/port/path.c | 17 +++++++++++++++++ + 3 files changed, 41 insertions(+) + +diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c +index a807086..faaa5ce 100644 +--- a/src/bin/pg_rewind/file_ops.c ++++ b/src/bin/pg_rewind/file_ops.c +@@ -48,6 +48,9 @@ open_target_file(const char *path, bool trunc) + { + int mode; + ++ if (!path_is_safe_for_extraction(path)) ++ pg_fatal("target file path is unsafe for open: \"%s\"", path); ++ + if (dry_run) + return; + +@@ -188,6 +191,9 @@ remove_target_file(const char *path, bool missing_ok) + { + char dstpath[MAXPGPATH]; + ++ if (!path_is_safe_for_extraction(path)) ++ pg_fatal("target file path is unsafe for removal: \"%s\"", path); ++ + if (dry_run) + return; + +@@ -208,6 +214,9 @@ truncate_target_file(const char *path, off_t newsize) + char dstpath[MAXPGPATH]; + int fd; + ++ if (!path_is_safe_for_extraction(path)) ++ pg_fatal("target file path is unsafe for truncation: \"%s\"", path); ++ + if (dry_run) + return; + +@@ -230,6 +239,10 @@ create_target_dir(const char *path) + { + char dstpath[MAXPGPATH]; + ++ if (!path_is_safe_for_extraction(path)) ++ pg_fatal("target directory path is unsafe for directory creation: \"%s\"", ++ path); ++ + if (dry_run) + return; + +@@ -244,6 +257,10 @@ remove_target_dir(const char *path) + { + char dstpath[MAXPGPATH]; + ++ if (!path_is_safe_for_extraction(path)) ++ pg_fatal("target directory path is unsafe for directory removal: \"%s\"", ++ path); ++ + if (dry_run) + return; + +@@ -258,6 +275,9 @@ create_target_symlink(const char *path, const char *link) + { + char dstpath[MAXPGPATH]; + ++ if (!path_is_safe_for_extraction(path)) ++ pg_fatal("target symlink path is unsafe for creation: \"%s\"", path); ++ + if (dry_run) + return; + +@@ -272,6 +292,9 @@ remove_target_symlink(const char *path) + { + char dstpath[MAXPGPATH]; + ++ if (!path_is_safe_for_extraction(path)) ++ pg_fatal("target symlink path is unsafe for removal: \"%s\"", path); ++ + if (dry_run) + return; + +diff --git a/src/include/port.h b/src/include/port.h +index db53dec..a257d8d 100644 +--- a/src/include/port.h ++++ b/src/include/port.h +@@ -55,6 +55,7 @@ extern void make_native_path(char *path); + extern void cleanup_path(char *path); + extern bool path_contains_parent_reference(const char *path); + extern bool path_is_relative_and_below_cwd(const char *path); ++extern bool path_is_safe_for_extraction(const char *path); + extern bool path_is_prefix_of_path(const char *path1, const char *path2); + extern char *make_absolute_path(const char *path); + extern const char *get_progname(const char *argv0); +diff --git a/src/port/path.c b/src/port/path.c +index d37a484..117f919 100644 +--- a/src/port/path.c ++++ b/src/port/path.c +@@ -505,6 +505,23 @@ path_is_relative_and_below_cwd(const char *path) + return true; + } + ++/* ++ * Detect whether a path is safe for use during archive extraction. ++ * ++ * This applies canonicalize_path(), then it checks that the path does ++ * not contain any parent directory references. ++ */ ++bool ++path_is_safe_for_extraction(const char *path) ++{ ++ char buf[MAXPGPATH]; ++ ++ strlcpy(buf, path, sizeof(buf)); ++ canonicalize_path(buf); ++ ++ return path_is_relative_and_below_cwd(buf); ++} ++ + /* + * Detect whether path1 is a prefix of path2 (including equality). + * +-- +2.39.5 (Apple Git-154) + diff --git a/SOURCES/postgresql-CVE-2026-6477.patch b/SOURCES/postgresql-CVE-2026-6477.patch new file mode 100644 index 0000000..83a5e66 --- /dev/null +++ b/SOURCES/postgresql-CVE-2026-6477.patch @@ -0,0 +1,219 @@ +From 2b450648240c6c56f367e74bbf0f785b91f93396 Mon Sep 17 00:00:00 2001 +From: Petr Khartskhaev +Date: Wed, 3 Jun 2026 16:34:32 +0200 +Subject: [PATCH] fix CVE-2026-6477 + +--- + doc/src/sgml/libpq.sgml | 11 +- + src/interfaces/libpq/fe-exec.c | 18 +- + src/interfaces/libpq/fe-lobj.c | 12 +- + src/interfaces/libpq/fe-protocol2.c | 16 +- + src/interfaces/libpq/fe-protocol3.c | 14 +- + src/interfaces/libpq/libpq-int.h | 9 +- + 13 files changed, 17098 insertions(+), 15 deletions(-) + +diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml +index 1a84241..e1f688c 100644 +--- a/doc/src/sgml/libpq.sgml ++++ b/doc/src/sgml/libpq.sgml +@@ -5086,15 +5086,20 @@ int PQrequestCancel(PGconn *conn); + to send simple function calls to the server. + + +- ++ + +- This interface is somewhat obsolete, as one can achieve similar ++ This interface is unsafe and should not be used. When ++ result_is_int is set to 0, ++ PQfn may write data beyond the end of ++ result_buf, regardless of whether the buffer has ++ enough space for the requested number of bytes. Furthermore, it is ++ obsolete, as one can achieve similar + performance and greater functionality by setting up a prepared + statement to define the function call. Then, executing the statement + with binary transmission of parameters and results substitutes for a + fast-path function call. + +- ++ + + + The function PQfnPQfn +diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c +index b30b565..076a9d0 100644 +--- a/src/interfaces/libpq/fe-exec.c ++++ b/src/interfaces/libpq/fe-exec.c +@@ -2665,6 +2665,20 @@ PQfn(PGconn *conn, + int result_is_int, + const PQArgBlock *args, + int nargs) ++{ ++ return PQnfn(conn, fnid, result_buf, -1, result_len, ++ result_is_int, args, nargs); ++} ++ ++/* ++ * PQnfn ++ * Private version of PQfn() with verification that returned data fits in ++ * result_buf when result_is_int == 0. Setting buf_size to -1 disables ++ * this verification. ++ */ ++PGresult * ++PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size, int *result_len, ++ int result_is_int, const PQArgBlock *args, int nargs) + { + *result_len = 0; + +@@ -2684,12 +2698,12 @@ PQfn(PGconn *conn, + + if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3) + return pqFunctionCall3(conn, fnid, +- result_buf, result_len, ++ result_buf, buf_size, result_len, + result_is_int, + args, nargs); + else + return pqFunctionCall2(conn, fnid, +- result_buf, result_len, ++ result_buf, buf_size, result_len, + result_is_int, + args, nargs); + } +diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c +index a94ce06..01e67a3 100644 +--- a/src/interfaces/libpq/fe-lobj.c ++++ b/src/interfaces/libpq/fe-lobj.c +@@ -288,8 +288,8 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) + argv[1].len = 4; + argv[1].u.integer = (int) len; + +- res = PQfn(conn, conn->lobjfuncs->fn_lo_read, +- (void *) buf, &result_len, 0, argv, 2); ++ res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, ++ (void *) buf, len, &result_len, 0, argv, 2); + if (PQresultStatus(res) == PGRES_COMMAND_OK) + { + PQclear(res); +@@ -439,8 +439,8 @@ lo_lseek64(PGconn *conn, int fd, pg_int64 offset, int whence) + argv[2].len = 4; + argv[2].u.integer = whence; + +- res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek64, +- (void *) &retval, &result_len, 0, argv, 3); ++ res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, ++ (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); + if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + { + PQclear(res); +@@ -605,8 +605,8 @@ lo_tell64(PGconn *conn, int fd) + argv[0].len = 4; + argv[0].u.integer = fd; + +- res = PQfn(conn, conn->lobjfuncs->fn_lo_tell64, +- (void *) &retval, &result_len, 0, argv, 1); ++ res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, ++ (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); + if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + { + PQclear(res); +diff --git a/src/interfaces/libpq/fe-protocol2.c b/src/interfaces/libpq/fe-protocol2.c +index 9360c54..0c2b629 100644 +--- a/src/interfaces/libpq/fe-protocol2.c ++++ b/src/interfaces/libpq/fe-protocol2.c +@@ -1428,7 +1428,7 @@ pqEndcopy2(PGconn *conn) + */ + PGresult * + pqFunctionCall2(PGconn *conn, Oid fnid, +- int *result_buf, int *actual_result_len, ++ int *result_buf, int buf_size, int *actual_result_len, + int result_is_int, + const PQArgBlock *args, int nargs) + { +@@ -1510,6 +1510,20 @@ pqFunctionCall2(PGconn *conn, Oid fnid, + } + else + { ++ /* ++ * If the server returned too much data for the ++ * buffer, something fishy is going on. Abandon ship. ++ */ ++ if (buf_size != -1 && *actual_result_len > buf_size) ++ { ++ appendPQExpBufferStr(&conn->errorMessage, ++ libpq_gettext("server returned too much data\n")); ++ conn->asyncStatus = PGASYNC_READY; ++ pqDropConnection(conn, true); ++ conn->status = CONNECTION_BAD; ++ return pqPrepareAsyncResult(conn); ++ } ++ + if (pqGetnchar((char *) result_buf, + *actual_result_len, + conn)) +diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c +index 3da1ace..a092010 100644 +--- a/src/interfaces/libpq/fe-protocol3.c ++++ b/src/interfaces/libpq/fe-protocol3.c +@@ -1903,7 +1903,7 @@ pqEndcopy3(PGconn *conn) + */ + PGresult * + pqFunctionCall3(PGconn *conn, Oid fnid, +- int *result_buf, int *actual_result_len, ++ int *result_buf, int buf_size, int *actual_result_len, + int result_is_int, + const PQArgBlock *args, int nargs) + { +@@ -2034,6 +2034,18 @@ pqFunctionCall3(PGconn *conn, Oid fnid, + } + else + { ++ /* ++ * If the server returned too much data for the ++ * buffer, something fishy is going on. Abandon ship. ++ */ ++ if (buf_size != -1 && *actual_result_len > buf_size) ++ { ++ appendPQExpBufferStr(&conn->errorMessage, ++ libpq_gettext("server returned too much data\n")); ++ handleSyncLoss(conn, id, *actual_result_len); ++ return pqPrepareAsyncResult(conn); ++ } ++ + if (pqGetnchar((char *) result_buf, + *actual_result_len, + conn)) +diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h +index 814884a..1b0cf91 100644 +--- a/src/interfaces/libpq/libpq-int.h ++++ b/src/interfaces/libpq/libpq-int.h +@@ -622,6 +622,9 @@ extern void pqSaveMessageField(PGresult *res, char code, + extern void pqSaveParameterStatus(PGconn *conn, const char *name, + const char *value); + extern int pqRowProcessor(PGconn *conn, const char **errmsgp); ++extern PGresult *PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size, ++ int *result_len, int result_is_int, ++ const PQArgBlock *args, int nargs); + + /* === in fe-protocol2.c === */ + +@@ -635,7 +638,8 @@ extern int pqGetline2(PGconn *conn, char *s, int maxlen); + extern int pqGetlineAsync2(PGconn *conn, char *buffer, int bufsize); + extern int pqEndcopy2(PGconn *conn); + extern PGresult *pqFunctionCall2(PGconn *conn, Oid fnid, +- int *result_buf, int *actual_result_len, ++ int *result_buf, int buf_size, ++ int *actual_result_len, + int result_is_int, + const PQArgBlock *args, int nargs); + +@@ -652,7 +656,8 @@ extern int pqGetline3(PGconn *conn, char *s, int maxlen); + extern int pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize); + extern int pqEndcopy3(PGconn *conn); + extern PGresult *pqFunctionCall3(PGconn *conn, Oid fnid, +- int *result_buf, int *actual_result_len, ++ int *result_buf, int buf_size, ++ int *actual_result_len, + int result_is_int, + const PQArgBlock *args, int nargs); + diff --git a/SOURCES/postgresql-CVE-2026-6478.patch b/SOURCES/postgresql-CVE-2026-6478.patch new file mode 100644 index 0000000..20fbb47 --- /dev/null +++ b/SOURCES/postgresql-CVE-2026-6478.patch @@ -0,0 +1,250 @@ +Created by combining upstream commit 4608619a1cf578f16e799510eaa0a21c0f1f08e3 +that fixes the CVE +with upstream commit b282280e9b69cae988c0c69cce3eda4d4bd38fff +that provides the function the fix uses, `timingsafe_bcmp` + +diff --git a/configure b/configure +index 91df389..4b85063 100755 +--- a/configure ++++ b/configure +@@ -16305,6 +16305,16 @@ fi + cat >>confdefs.h <<_ACEOF + #define HAVE_DECL_STRNLEN $ac_have_decl + _ACEOF ++ac_fn_c_check_decl "$LINENO" "timingsafe_bcmp" "ac_cv_have_decl_timingsafe_bcmp" "$ac_includes_default" ++if test "x$ac_cv_have_decl_timingsafe_bcmp" = xyes; then : ++ ac_have_decl=1 ++else ++ ac_have_decl=0 ++fi ++ ++cat >>confdefs.h <<_ACEOF ++#define HAVE_DECL_TIMINGSAFE_BCMP $ac_have_decl ++_ACEOF + + + # We can't use AC_REPLACE_FUNCS to replace these functions, because it +@@ -16639,6 +16649,19 @@ esac + + fi + ++ac_fn_c_check_func "$LINENO" "timingsafe_bcmp" "ac_cv_func_timingsafe_bcmp" ++if test "x$ac_cv_func_timingsafe_bcmp" = xyes; then : ++ $as_echo "#define HAVE_TIMINGSAFE_BCMP 1" >>confdefs.h ++ ++else ++ case " $LIBOBJS " in ++ *" timingsafe_bcmp.$ac_objext "* ) ;; ++ *) LIBOBJS="$LIBOBJS timingsafe_bcmp.$ac_objext" ++ ;; ++esac ++ ++fi ++ + + + if test "$PORTNAME" = "win32" -o "$PORTNAME" = "cygwin"; then +diff --git a/configure.in b/configure.in +index 318851d..5461fc6 100644 +--- a/configure.in ++++ b/configure.in +@@ -1793,7 +1793,7 @@ AC_CHECK_DECLS(posix_fadvise, [], [], [#include ]) + ]) # fi + + AC_CHECK_DECLS(fdatasync, [], [], [#include ]) +-AC_CHECK_DECLS([strlcat, strlcpy, strnlen]) ++AC_CHECK_DECLS([strlcat, strlcpy, strnlen, timingsafe_bcmp]) + + # We can't use AC_REPLACE_FUNCS to replace these functions, because it + # won't handle deployment target restrictions on macOS +@@ -1843,6 +1843,7 @@ AC_REPLACE_FUNCS(m4_normalize([ + strlcpy + strnlen + strtof ++ timingsafe_bcmp + ])) + + if test "$PORTNAME" = "win32" -o "$PORTNAME" = "cygwin"; then +diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c +index 5214d32..75c97ca 100644 +--- a/src/backend/libpq/auth-scram.c ++++ b/src/backend/libpq/auth-scram.c +@@ -537,7 +537,7 @@ scram_verify_plain_password(const char *username, const char *password, + * Compare the secret's Server Key with the one computed from the + * user-supplied password. + */ +- return memcmp(computed_key, server_key, SCRAM_KEY_LEN) == 0; ++ return timingsafe_bcmp(computed_key, server_key, SCRAM_KEY_LEN) == 0; + } + + +@@ -1074,9 +1074,9 @@ verify_final_nonce(scram_state *state) + + if (final_nonce_len != client_nonce_len + server_nonce_len) + return false; +- if (memcmp(state->client_final_nonce, state->client_nonce, client_nonce_len) != 0) ++ if (timingsafe_bcmp(state->client_final_nonce, state->client_nonce, client_nonce_len) != 0) + return false; +- if (memcmp(state->client_final_nonce + client_nonce_len, state->server_nonce, server_nonce_len) != 0) ++ if (timingsafe_bcmp(state->client_final_nonce + client_nonce_len, state->server_nonce, server_nonce_len) != 0) + return false; + + return true; +@@ -1117,7 +1117,7 @@ verify_client_proof(scram_state *state) + /* Hash it one more time, and compare with StoredKey */ + scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey); + +- if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0) ++ if (timingsafe_bcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0) + return false; + + return true; +diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c +index 8b63217..e56eab1 100644 +--- a/src/backend/libpq/auth.c ++++ b/src/backend/libpq/auth.c +@@ -3375,7 +3375,7 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por + } + pfree(cryptvector); + +- if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0) ++ if (timingsafe_bcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0) + { + ereport(LOG, + (errmsg("RADIUS response from %s has incorrect MD5 signature", +diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c +index 17b91ac..7f3aea5 100644 +--- a/src/backend/libpq/crypt.c ++++ b/src/backend/libpq/crypt.c +@@ -196,7 +196,8 @@ md5_crypt_verify(const char *role, const char *shadow_pass, + return STATUS_ERROR; + } + +- if (strcmp(client_pass, crypt_pwd) == 0) ++ if (strlen(client_pass) == strlen(crypt_pwd) && ++ timingsafe_bcmp(client_pass, crypt_pwd, strlen(crypt_pwd)) == 0) + retval = STATUS_OK; + else + { +@@ -261,7 +262,8 @@ plain_crypt_verify(const char *role, const char *shadow_pass, + */ + return STATUS_ERROR; + } +- if (strcmp(crypt_client_pass, shadow_pass) == 0) ++ if (strlen(crypt_client_pass) == strlen(shadow_pass) && ++ timingsafe_bcmp(crypt_client_pass, shadow_pass, strlen(shadow_pass)) == 0) + return STATUS_OK; + else + { +diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in +index b351a0b..d86ad1d 100644 +--- a/src/include/pg_config.h.in ++++ b/src/include/pg_config.h.in +@@ -175,6 +175,10 @@ + don't. */ + #undef HAVE_DECL_STRTOULL + ++/* Define to 1 if you have the declaration of `timingsafe_bcmp', and to 0 if ++ you don't. */ ++#undef HAVE_DECL_TIMINGSAFE_BCMP ++ + /* Define to 1 if you have the `dlopen' function. */ + #undef HAVE_DLOPEN + +@@ -634,6 +638,9 @@ + /* Define to 1 if you have the header file. */ + #undef HAVE_TERMIOS_H + ++/* Define to 1 if you have the `timingsafe_bcmp' function. */ ++#undef HAVE_TIMINGSAFE_BCMP ++ + /* Define to 1 if your compiler understands `typeof' or something similar. */ + #undef HAVE_TYPEOF + +diff --git a/src/include/port.h b/src/include/port.h +index 0e86dfb..d3ca64e 100644 +--- a/src/include/port.h ++++ b/src/include/port.h +@@ -514,6 +514,10 @@ extern int pqGethostbyname(const char *name, + struct hostent **result, + int *herrno); + ++#if !HAVE_DECL_TIMINGSAFE_BCMP ++extern int timingsafe_bcmp(const void *b1, const void *b2, size_t len); ++#endif ++ + extern void pg_qsort(void *base, size_t nel, size_t elsize, + int (*cmp) (const void *, const void *)); + extern int pg_qsort_strcmp(const void *a, const void *b); +diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c +index 3e705f8..065244c 100644 +--- a/src/interfaces/libpq/fe-auth-scram.c ++++ b/src/interfaces/libpq/fe-auth-scram.c +@@ -612,7 +612,7 @@ read_server_first_message(fe_scram_state *state, char *input) + + /* Verify immediately that the server used our part of the nonce */ + if (strlen(nonce) < strlen(state->client_nonce) || +- memcmp(nonce, state->client_nonce, strlen(state->client_nonce)) != 0) ++ timingsafe_bcmp(nonce, state->client_nonce, strlen(state->client_nonce)) != 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid SCRAM response (nonce mismatch)\n")); +@@ -814,7 +814,8 @@ verify_server_signature(fe_scram_state *state) + strlen(state->client_final_message_without_proof)); + scram_HMAC_final(expected_ServerSignature, &ctx); + +- if (memcmp(expected_ServerSignature, state->ServerSignature, SCRAM_KEY_LEN) != 0) ++ if (timingsafe_bcmp(expected_ServerSignature, state->ServerSignature, ++ SCRAM_KEY_LEN) != 0) + return false; + + return true; +diff --git a/src/port/timingsafe_bcmp.c b/src/port/timingsafe_bcmp.c +new file mode 100644 +index 0000000..288865f +--- /dev/null ++++ b/src/port/timingsafe_bcmp.c +@@ -0,0 +1,43 @@ ++/* ++ * src/port/timingsafe_bcmp.c ++ * ++ * $OpenBSD: timingsafe_bcmp.c,v 1.3 2015/08/31 02:53:57 guenther Exp $ ++ */ ++ ++/* ++ * Copyright (c) 2010 Damien Miller. All rights reserved. ++ * ++ * Permission to use, copy, modify, and distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#include "c.h" ++ ++#ifdef USE_SSL ++#include ++#endif ++ ++int ++timingsafe_bcmp(const void *b1, const void *b2, size_t n) ++{ ++#ifdef USE_SSL ++ return CRYPTO_memcmp(b1, b2, n); ++#else ++ const unsigned char *p1 = b1, ++ *p2 = b2; ++ int ret = 0; ++ ++ for (; n > 0; n--) ++ ret |= *p1++ ^ *p2++; ++ return (ret != 0); ++#endif ++} diff --git a/SOURCES/postgresql-CVE-2026-6637.patch b/SOURCES/postgresql-CVE-2026-6637.patch new file mode 100644 index 0000000..b95f0ea --- /dev/null +++ b/SOURCES/postgresql-CVE-2026-6637.patch @@ -0,0 +1,203 @@ +From 2b026df29c1e1caafb259a2021528a28ec484018 Mon Sep 17 00:00:00 2001 +From: Nathan Bossart +Date: Mon, 11 May 2026 05:13:52 -0700 +Subject: [PATCH] refint: Fix SQL injection and buffer overruns. + +Maliciously crafted key value updates could achieve SQL injection +within check_foreign_key(). To fix, ensure new key values are +properly quoted and escaped in the internally generated SQL +statements. While at it, avoid potential buffer overruns by +replacing the stack buffers for internally generated SQL statements +with StringInfo. + +Reported-by: Nikolay Samokhvalov +Author: Nathan Bossart +Reviewed-by: Noah Misch +Reviewed-by: Tom Lane +Reviewed-by: Fujii Masao +Security: CVE-2026-6637 +Backpatch-through: 14 +--- + contrib/spi/refint.c | 84 ++++++++++++++++++++------------------------ + 1 file changed, 38 insertions(+), 46 deletions(-) + +diff --git a/contrib/spi/refint.c b/contrib/spi/refint.c +index 6fbfef2..cbef463 100644 +--- a/contrib/spi/refint.c ++++ b/contrib/spi/refint.c +@@ -166,21 +166,24 @@ check_primary_key(PG_FUNCTION_ARGS) + if (plan->nplans <= 0) + { + SPIPlanPtr pplan; +- char sql[8192]; ++ StringInfoData sql; ++ ++ initStringInfo(&sql); + + /* + * Construct query: SELECT 1 FROM _referenced_relation_ WHERE Pkey1 = + * $1 [AND Pkey2 = $2 [...]] + */ +- snprintf(sql, sizeof(sql), "select 1 from %s where ", relname); +- for (i = 0; i < nkeys; i++) ++ appendStringInfo(&sql, "select 1 from %s where ", relname); ++ for (i = 1; i <= nkeys; i++) + { +- snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s", +- args[i + nkeys + 1], i + 1, (i < nkeys - 1) ? "and " : ""); ++ appendStringInfo(&sql, "%s = $%d ", args[i + nkeys], i); ++ if (i < nkeys) ++ appendStringInfoString(&sql, "and "); + } + + /* Prepare plan for query */ +- pplan = SPI_prepare(sql, nkeys, argtypes); ++ pplan = SPI_prepare(sql.data, nkeys, argtypes); + if (pplan == NULL) + /* internal error */ + elog(ERROR, "check_primary_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result)); +@@ -196,6 +199,8 @@ check_primary_key(PG_FUNCTION_ARGS) + sizeof(SPIPlanPtr)); + *(plan->splan) = pplan; + plan->nplans = 1; ++ ++ pfree(sql.data); + } + + /* +@@ -416,7 +421,6 @@ check_foreign_key(PG_FUNCTION_ARGS) + if (plan->nplans <= 0) + { + SPIPlanPtr pplan; +- char sql[8192]; + char **args2 = args; + + plan->splan = (SPIPlanPtr *) MemoryContextAlloc(TopMemoryContext, +@@ -424,6 +428,10 @@ check_foreign_key(PG_FUNCTION_ARGS) + + for (r = 0; r < nrefs; r++) + { ++ StringInfoData sql; ++ ++ initStringInfo(&sql); ++ + relname = args2[0]; + + /*--------- +@@ -437,8 +445,7 @@ check_foreign_key(PG_FUNCTION_ARGS) + *--------- + */ + if (action == 'r') +- +- snprintf(sql, sizeof(sql), "select 1 from %s where ", relname); ++ appendStringInfo(&sql, "select 1 from %s where ", relname); + + /*--------- + * For 'C'ascade action we construct DELETE query +@@ -465,43 +472,24 @@ check_foreign_key(PG_FUNCTION_ARGS) + char *nv; + int k; + +- snprintf(sql, sizeof(sql), "update %s set ", relname); ++ appendStringInfo(&sql, "update %s set ", relname); + for (k = 1; k <= nkeys; k++) + { +- int is_char_type = 0; +- char *type; +- + fn = SPI_fnumber(tupdesc, args_temp[k - 1]); + Assert(fn > 0); /* already checked above */ + nv = SPI_getvalue(newtuple, tupdesc, fn); +- type = SPI_gettype(tupdesc, fn); +- +- if (strcmp(type, "text") == 0 || +- strcmp(type, "varchar") == 0 || +- strcmp(type, "char") == 0 || +- strcmp(type, "bpchar") == 0 || +- strcmp(type, "date") == 0 || +- strcmp(type, "timestamp") == 0) +- is_char_type = 1; +-#ifdef DEBUG_QUERY +- elog(DEBUG4, "check_foreign_key Debug value %s type %s %d", +- nv, type, is_char_type); +-#endif + +- /* +- * is_char_type =1 i set ' ' for define a new value +- */ +- snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), +- " %s = %s%s%s %s ", +- args2[k], (is_char_type > 0) ? "'" : "", +- nv, (is_char_type > 0) ? "'" : "", (k < nkeys) ? ", " : ""); ++ appendStringInfo(&sql, " %s = %s ", ++ args2[k], quote_literal_cstr(nv)); ++ if (k < nkeys) ++ appendStringInfoString(&sql, ", "); + } +- strcat(sql, " where "); ++ appendStringInfoString(&sql, " where "); + + } + else + /* DELETE */ +- snprintf(sql, sizeof(sql), "delete from %s where ", relname); ++ appendStringInfo(&sql, "delete from %s where ", relname); + + } + +@@ -513,25 +501,26 @@ check_foreign_key(PG_FUNCTION_ARGS) + */ + else if (action == 's') + { +- snprintf(sql, sizeof(sql), "update %s set ", relname); ++ appendStringInfo(&sql, "update %s set ", relname); + for (i = 1; i <= nkeys; i++) + { +- snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), +- "%s = null%s", +- args2[i], (i < nkeys) ? ", " : ""); ++ appendStringInfo(&sql, "%s = null", args2[i]); ++ if (i < nkeys) ++ appendStringInfoString(&sql, ", "); + } +- strcat(sql, " where "); ++ appendStringInfoString(&sql, " where "); + } + + /* Construct WHERE qual */ + for (i = 1; i <= nkeys; i++) + { +- snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s", +- args2[i], i, (i < nkeys) ? "and " : ""); ++ appendStringInfo(&sql, "%s = $%d ", args2[i], i); ++ if (i < nkeys) ++ appendStringInfoString(&sql, "and "); + } + + /* Prepare plan for query */ +- pplan = SPI_prepare(sql, nkeys, argtypes); ++ pplan = SPI_prepare(sql.data, nkeys, argtypes); + if (pplan == NULL) + /* internal error */ + elog(ERROR, "check_foreign_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result)); +@@ -547,11 +536,14 @@ check_foreign_key(PG_FUNCTION_ARGS) + plan->splan[r] = pplan; + + args2 += nkeys + 1; /* to the next relation */ ++ ++#ifdef DEBUG_QUERY ++ elog(DEBUG4, "check_foreign_key Debug Query is : %s ", sql.data); ++#endif ++ ++ pfree(sql.data); + } + plan->nplans = nrefs; +-#ifdef DEBUG_QUERY +- elog(DEBUG4, "check_foreign_key Debug Query is : %s ", sql); +-#endif + } + + /* +-- +2.39.5 (Apple Git-154) + diff --git a/SPECS/postgresql.spec b/SPECS/postgresql.spec index 5825d59..43f6b5d 100644 --- a/SPECS/postgresql.spec +++ b/SPECS/postgresql.spec @@ -60,7 +60,7 @@ Summary: PostgreSQL client programs Name: postgresql %global majorversion 13 Version: %{majorversion}.23 -Release: 2%{?dist} +Release: 3%{?dist} # The PostgreSQL license is very similar to other MIT licenses, but the OSI # recognizes it as an independent license, so we do as well. @@ -108,10 +108,17 @@ Patch6: postgresql-man.patch Patch8: postgresql-external-libpq.patch Patch9: postgresql-server-pg_config.patch Patch10: CVE-2026-2004--CVE-2026-2005--CVE-2026-2006.patch +Patch15: postgresql-CVE-2026-6478.patch +Patch16: postgresql-CVE-2026-6637.patch +Patch17: postgresql-CVE-2026-6477.patch +Patch18: postgresql-CVE-2026-6475.patch +Patch19: postgresql-CVE-2026-6473.patch BuildRequires: gcc BuildRequires: perl(ExtUtils::MakeMaker) glibc-devel bison flex gawk BuildRequires: perl(ExtUtils::Embed), perl-devel +BuildRequires: perl(Opcode) +BuildRequires: perl(FindBin) %if 0%{?fedora} || 0%{?rhel} > 7 BuildRequires: perl-generators %endif @@ -119,6 +126,7 @@ BuildRequires: readline-devel zlib-devel BuildRequires: systemd systemd-devel util-linux BuildRequires: multilib-rpm-config BuildRequires: libpq-devel +BuildRequires: docbook-style-xsl # postgresql-setup build requires BuildRequires: m4 elinks docbook-utils help2man @@ -369,6 +377,11 @@ benchmarks. %patch8 -p1 %patch9 -p1 %patch10 -p1 +%patch15 -p1 +%patch16 -p1 +%patch17 -p1 +%patch18 -p1 +%patch19 -p1 # We used to run autoconf here, but there's no longer any real need to, # since Postgres ships with a reasonably modern configure script. @@ -1226,6 +1239,11 @@ make -C postgresql-setup-%{setup_version} check %changelog +* Wed Jun 3 2026 Petr Khartskhaev - 13.23-3 +- Backport fix for CVE-2026-6478 from PostgreSQL 14.23 +- Backport fixes for CVE-2026-6637, CVE-2026-6477, CVE-2026-6475, CVE-2026-6473 +- Resolves: RHEL-179806 + * Wed Feb 25 2026 Filip Janus - 13.23-2 - fix CVE-2026-2004 CVE-2026-2005 CVE-2026-2006