From dd8af778d2292bd8796a4df21d8f17721ed8440c Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 11 May 2026 05:13:48 -0700 Subject: [PATCH] Fix integer-overflow and alignment hazards in locale-related code. pg_locale_icu.c was full of places where a very long input string could cause integer overflow while calculating a buffer size, leading to buffer overruns. It also was cavalier about using char-type local arrays as buffers holding arrays of UChar. The alignment of a char[] variable isn't guaranteed, so that this risked failure on alignment-picky platforms. The lack of complaints suggests that such platforms are very rare nowadays; but it's likely that we are paying a performance price on rather more platforms. Declare those arrays as UChar[] instead, keeping their physical size the same. pg_locale_libc.c's strncoll_libc_win32_utf8() also had the disease of assuming it could double or quadruple the input string length without concern for overflow. Reported-by: Xint Code Reported-by: Pavel Kohout Author: Tom Lane Backpatch-through: 14 Security: CVE-2026-6473 --- src/backend/utils/adt/pg_locale_icu.c | 77 +++++++++++--------------- src/backend/utils/adt/pg_locale_libc.c | 18 ++++-- 2 files changed, 45 insertions(+), 50 deletions(-) diff --git a/src/backend/utils/adt/pg_locale_icu.c b/src/backend/utils/adt/pg_locale_icu.c index 4f401a28f0d..72c5fffa204 100644 --- a/src/backend/utils/adt/pg_locale_icu.c +++ b/src/backend/utils/adt/pg_locale_icu.c @@ -95,8 +95,8 @@ static size_t strnxfrm_prefix_icu_utf8(char *dest, size_t destsize, const char *src, ssize_t srclen, pg_locale_t locale); static void init_icu_converter(void); -static size_t uchar_length(UConverter *converter, - const char *str, int32_t len); +static int32_t uchar_length(UConverter *converter, + const char *str, int32_t len); static int32_t uchar_convert(UConverter *converter, UChar *dest, int32_t destlen, const char *src, int32_t srclen); @@ -349,7 +349,7 @@ make_icu_collator(const char *iculocstr, const char *icurules) total = u_strlen(std_rules) + u_strlen(my_rules) + 1; /* avoid leaking collator on OOM */ - all_rules = palloc_extended(sizeof(UChar) * total, MCXT_ALLOC_NO_OOM); + all_rules = palloc_array_extended(UChar, total, MCXT_ALLOC_NO_OOM); if (!all_rules) { ucol_close(collator_std_rules); @@ -498,11 +498,9 @@ size_t strnxfrm_icu(char *dest, size_t destsize, const char *src, ssize_t srclen, pg_locale_t locale) { - char sbuf[TEXTBUFLEN]; - char *buf = sbuf; - UChar *uchar; + UChar sbuf[TEXTBUFLEN / sizeof(UChar)]; + UChar *uchar = sbuf; int32_t ulen; - size_t uchar_bsize; Size result_bsize; Assert(locale->provider == COLLPROVIDER_ICU); @@ -511,12 +509,8 @@ strnxfrm_icu(char *dest, size_t destsize, const char *src, ssize_t srclen, ulen = uchar_length(icu_converter, src, srclen); - uchar_bsize = (ulen + 1) * sizeof(UChar); - - if (uchar_bsize > TEXTBUFLEN) - buf = palloc(uchar_bsize); - - uchar = (UChar *) buf; + if (ulen >= lengthof(sbuf)) + uchar = palloc_array(UChar, ulen + 1); ulen = uchar_convert(icu_converter, uchar, ulen + 1, src, srclen); @@ -531,8 +525,8 @@ strnxfrm_icu(char *dest, size_t destsize, const char *src, ssize_t srclen, Assert(result_bsize > 0); result_bsize--; - if (buf != sbuf) - pfree(buf); + if (uchar != sbuf) + pfree(uchar); /* if dest is defined, it should be nul-terminated */ Assert(result_bsize >= destsize || dest[result_bsize] == '\0'); @@ -609,7 +603,7 @@ icu_to_uchar(UChar **buff_uchar, const char *buff, size_t nbytes) len_uchar = uchar_length(icu_converter, buff, nbytes); - *buff_uchar = palloc((len_uchar + 1) * sizeof(**buff_uchar)); + *buff_uchar = palloc_array(UChar, len_uchar + 1); len_uchar = uchar_convert(icu_converter, *buff_uchar, len_uchar + 1, buff, nbytes); @@ -666,7 +660,7 @@ icu_convert_case(ICU_Convert_Func func, pg_locale_t mylocale, int32_t len_dest; len_dest = len_source; /* try first with same length */ - *buff_dest = palloc(len_dest * sizeof(**buff_dest)); + *buff_dest = palloc_array(UChar, len_dest); status = U_ZERO_ERROR; len_dest = func(*buff_dest, len_dest, buff_source, len_source, mylocale->info.icu.locale, &status); @@ -674,7 +668,7 @@ icu_convert_case(ICU_Convert_Func func, pg_locale_t mylocale, { /* try again with adjusted length */ pfree(*buff_dest); - *buff_dest = palloc(len_dest * sizeof(**buff_dest)); + *buff_dest = palloc_array(UChar, len_dest); status = U_ZERO_ERROR; len_dest = func(*buff_dest, len_dest, buff_source, len_source, mylocale->info.icu.locale, &status); @@ -741,12 +735,11 @@ static int strncoll_icu(const char *arg1, ssize_t len1, const char *arg2, ssize_t len2, pg_locale_t locale) { - char sbuf[TEXTBUFLEN]; - char *buf = sbuf; + UChar sbuf[TEXTBUFLEN / sizeof(UChar)]; + UChar *buf = sbuf; int32_t ulen1; int32_t ulen2; - size_t bufsize1; - size_t bufsize2; + size_t bufsize; UChar *uchar1, *uchar2; int result; @@ -763,14 +756,13 @@ strncoll_icu(const char *arg1, ssize_t len1, ulen1 = uchar_length(icu_converter, arg1, len1); ulen2 = uchar_length(icu_converter, arg2, len2); - bufsize1 = (ulen1 + 1) * sizeof(UChar); - bufsize2 = (ulen2 + 1) * sizeof(UChar); + /* ulen1+1 or ulen2+1 doesn't risk overflow, but summing them might */ + bufsize = add_size(ulen1 + 1, ulen2 + 1); + if (bufsize > lengthof(sbuf)) + buf = palloc_array(UChar, bufsize); - if (bufsize1 + bufsize2 > TEXTBUFLEN) - buf = palloc(bufsize1 + bufsize2); - - uchar1 = (UChar *) buf; - uchar2 = (UChar *) (buf + bufsize1); + uchar1 = buf; + uchar2 = buf + ulen1 + 1; ulen1 = uchar_convert(icu_converter, uchar1, ulen1 + 1, arg1, len1); ulen2 = uchar_convert(icu_converter, uchar2, ulen2 + 1, arg2, len2); @@ -791,14 +783,12 @@ strnxfrm_prefix_icu(char *dest, size_t destsize, const char *src, ssize_t srclen, pg_locale_t locale) { - char sbuf[TEXTBUFLEN]; - char *buf = sbuf; + UChar sbuf[TEXTBUFLEN / sizeof(UChar)]; + UChar *uchar = sbuf; UCharIterator iter; uint32_t state[2]; UErrorCode status; - int32_t ulen = -1; - UChar *uchar = NULL; - size_t uchar_bsize; + int32_t ulen; Size result_bsize; Assert(locale->provider == COLLPROVIDER_ICU); @@ -810,12 +800,8 @@ strnxfrm_prefix_icu(char *dest, size_t destsize, ulen = uchar_length(icu_converter, src, srclen); - uchar_bsize = (ulen + 1) * sizeof(UChar); - - if (uchar_bsize > TEXTBUFLEN) - buf = palloc(uchar_bsize); - - uchar = (UChar *) buf; + if (ulen >= lengthof(sbuf)) + uchar = palloc_array(UChar, ulen + 1); ulen = uchar_convert(icu_converter, uchar, ulen + 1, src, srclen); @@ -833,8 +819,8 @@ strnxfrm_prefix_icu(char *dest, size_t destsize, (errmsg("sort key generation failed: %s", u_errorName(status)))); - if (buf != sbuf) - pfree(buf); + if (uchar != sbuf) + pfree(uchar); return result_bsize; } @@ -870,8 +856,12 @@ init_icu_converter(void) * Find length, in UChars, of given string if converted to UChar string. * * A length of -1 indicates that the input string is NUL-terminated. + * + * Note: given the assumption that the input string fits in MaxAllocSize, + * the result cannot overflow int32_t. But callers must be careful about + * multiplying the result by sizeof(UChar). */ -static size_t +static int32_t uchar_length(UConverter *converter, const char *str, int32_t len) { UErrorCode status = U_ZERO_ERROR; @@ -897,7 +887,6 @@ uchar_convert(UConverter *converter, UChar *dest, int32_t destlen, UErrorCode status = U_ZERO_ERROR; int32_t ulen; - status = U_ZERO_ERROR; ulen = ucnv_toUChars(converter, dest, destlen, src, srclen, &status); if (U_FAILURE(status)) ereport(ERROR, diff --git a/src/backend/utils/adt/pg_locale_libc.c b/src/backend/utils/adt/pg_locale_libc.c index c68fec2e585..31274f069d2 100644 --- a/src/backend/utils/adt/pg_locale_libc.c +++ b/src/backend/utils/adt/pg_locale_libc.c @@ -737,8 +737,9 @@ strncoll_libc_win32_utf8(const char *arg1, ssize_t len1, const char *arg2, char *buf = sbuf; char *a1p, *a2p; - int a1len; - int a2len; + size_t a1len, + a2len, + buflen; int r; int result; @@ -750,11 +751,16 @@ strncoll_libc_win32_utf8(const char *arg1, ssize_t len1, const char *arg2, if (len2 == -1) len2 = strlen(arg2); - a1len = len1 * 2 + 2; - a2len = len2 * 2 + 2; + /* + * In a 32-bit build, twice the input length can overflow size_t, so we + * must be careful. + */ + a1len = add_size(add_size(len1, len1), 2); + a2len = add_size(add_size(len2, len2), 2); + buflen = add_size(a1len, a2len); - if (a1len + a2len > TEXTBUFLEN) - buf = palloc(a1len + a2len); + if (buflen > TEXTBUFLEN) + buf = palloc(buflen); a1p = buf; a2p = buf + a1len;