Skip to content

Commit 0cf2b56

Browse files
tglsfdcRucha Kulkarni
authored andcommitted
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 <pavel.kohout@aisle.com> Author: Tom Lane <tgl@sss.pgh.pa.us> Backpatch-through: 14 Security: CVE-2026-6473 (cherry picked from commit c259731242148834ac053dc3f6a4ffc3b317aa9a)
1 parent f065fc7 commit 0cf2b56

1 file changed

Lines changed: 43 additions & 45 deletions

File tree

src/backend/utils/adt/pg_locale.c

Lines changed: 43 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ static UConverter *icu_converter = NULL;
153153

154154
static UCollator *pg_ucol_open(const char *loc_str);
155155
static void init_icu_converter(void);
156-
static size_t uchar_length(UConverter *converter,
157-
const char *str, int32_t len);
156+
static int32_t uchar_length(UConverter *converter,
157+
const char *str, int32_t len);
158158
static int32_t uchar_convert(UConverter *converter,
159159
UChar *dest, int32_t destlen,
160160
const char *src, int32_t srclen);
@@ -1795,8 +1795,9 @@ pg_strncoll_libc_win32_utf8(const char *arg1, size_t len1, const char *arg2,
17951795
char *buf = sbuf;
17961796
char *a1p,
17971797
*a2p;
1798-
int a1len = len1 * 2 + 2;
1799-
int a2len = len2 * 2 + 2;
1798+
size_t a1len,
1799+
a2len,
1800+
buflen;
18001801
int r;
18011802
int result;
18021803

@@ -1806,8 +1807,16 @@ pg_strncoll_libc_win32_utf8(const char *arg1, size_t len1, const char *arg2,
18061807
Assert(false);
18071808
#endif
18081809

1809-
if (a1len + a2len > TEXTBUFLEN)
1810-
buf = palloc(a1len + a2len);
1810+
/*
1811+
* In a 32-bit build, twice the input length can overflow size_t, so we
1812+
* must be careful.
1813+
*/
1814+
a1len = add_size(add_size(len1, len1), 2);
1815+
a2len = add_size(add_size(len2, len2), 2);
1816+
buflen = add_size(a1len, a2len);
1817+
1818+
if (buflen > TEXTBUFLEN)
1819+
buf = palloc(buflen);
18111820

18121821
a1p = buf;
18131822
a2p = buf + a1len;
@@ -1958,12 +1967,11 @@ static int
19581967
pg_strncoll_icu_no_utf8(const char *arg1, int32_t len1,
19591968
const char *arg2, int32_t len2, pg_locale_t locale)
19601969
{
1961-
char sbuf[TEXTBUFLEN];
1962-
char *buf = sbuf;
1970+
UChar sbuf[TEXTBUFLEN / sizeof(UChar)];
1971+
UChar *buf = sbuf;
19631972
int32_t ulen1;
19641973
int32_t ulen2;
1965-
size_t bufsize1;
1966-
size_t bufsize2;
1974+
size_t bufsize;
19671975
UChar *uchar1,
19681976
*uchar2;
19691977
int result;
@@ -1978,14 +1986,13 @@ pg_strncoll_icu_no_utf8(const char *arg1, int32_t len1,
19781986
ulen1 = uchar_length(icu_converter, arg1, len1);
19791987
ulen2 = uchar_length(icu_converter, arg2, len2);
19801988

1981-
bufsize1 = (ulen1 + 1) * sizeof(UChar);
1982-
bufsize2 = (ulen2 + 1) * sizeof(UChar);
1989+
/* ulen1+1 or ulen2+1 doesn't risk overflow, but summing them might */
1990+
bufsize = add_size(ulen1 + 1, ulen2 + 1);
1991+
if (bufsize > lengthof(sbuf))
1992+
buf = palloc_array(UChar, bufsize);
19831993

1984-
if (bufsize1 + bufsize2 > TEXTBUFLEN)
1985-
buf = palloc(bufsize1 + bufsize2);
1986-
1987-
uchar1 = (UChar *) buf;
1988-
uchar2 = (UChar *) (buf + bufsize1);
1994+
uchar1 = buf;
1995+
uchar2 = buf + ulen1 + 1;
19891996

19901997
ulen1 = uchar_convert(icu_converter, uchar1, ulen1 + 1, arg1, len1);
19911998
ulen2 = uchar_convert(icu_converter, uchar2, ulen2 + 1, arg2, len2);
@@ -2166,11 +2173,9 @@ static size_t
21662173
pg_strnxfrm_icu(char *dest, const char *src, int32_t srclen, int32_t destsize,
21672174
pg_locale_t locale)
21682175
{
2169-
char sbuf[TEXTBUFLEN];
2170-
char *buf = sbuf;
2171-
UChar *uchar;
2176+
UChar sbuf[TEXTBUFLEN / sizeof(UChar)];
2177+
UChar *uchar = sbuf;
21722178
int32_t ulen;
2173-
size_t uchar_bsize;
21742179
Size result_bsize;
21752180

21762181
Assert(locale->provider == COLLPROVIDER_ICU);
@@ -2179,12 +2184,8 @@ pg_strnxfrm_icu(char *dest, const char *src, int32_t srclen, int32_t destsize,
21792184

21802185
ulen = uchar_length(icu_converter, src, srclen);
21812186

2182-
uchar_bsize = (ulen + 1) * sizeof(UChar);
2183-
2184-
if (uchar_bsize > TEXTBUFLEN)
2185-
buf = palloc(uchar_bsize);
2186-
2187-
uchar = (UChar *) buf;
2187+
if (ulen >= lengthof(sbuf))
2188+
uchar = palloc_array(UChar, ulen + 1);
21882189

21892190
ulen = uchar_convert(icu_converter, uchar, ulen + 1, src, srclen);
21902191

@@ -2199,8 +2200,8 @@ pg_strnxfrm_icu(char *dest, const char *src, int32_t srclen, int32_t destsize,
21992200
Assert(result_bsize > 0);
22002201
result_bsize--;
22012202

2202-
if (buf != sbuf)
2203-
pfree(buf);
2203+
if (uchar != sbuf)
2204+
pfree(uchar);
22042205

22052206
/* if dest is defined, it should be nul-terminated */
22062207
Assert(result_bsize >= destsize || dest[result_bsize] == '\0');
@@ -2213,14 +2214,12 @@ static size_t
22132214
pg_strnxfrm_prefix_icu_no_utf8(char *dest, const char *src, int32_t srclen,
22142215
int32_t destsize, pg_locale_t locale)
22152216
{
2216-
char sbuf[TEXTBUFLEN];
2217-
char *buf = sbuf;
2217+
UChar sbuf[TEXTBUFLEN / sizeof(UChar)];
2218+
UChar *uchar = sbuf;
22182219
UCharIterator iter;
22192220
uint32_t state[2];
22202221
UErrorCode status;
2221-
int32_t ulen = -1;
2222-
UChar *uchar = NULL;
2223-
size_t uchar_bsize;
2222+
int32_t ulen;
22242223
Size result_bsize;
22252224

22262225
Assert(locale->provider == COLLPROVIDER_ICU);
@@ -2230,12 +2229,8 @@ pg_strnxfrm_prefix_icu_no_utf8(char *dest, const char *src, int32_t srclen,
22302229

22312230
ulen = uchar_length(icu_converter, src, srclen);
22322231

2233-
uchar_bsize = (ulen + 1) * sizeof(UChar);
2234-
2235-
if (uchar_bsize > TEXTBUFLEN)
2236-
buf = palloc(uchar_bsize);
2237-
2238-
uchar = (UChar *) buf;
2232+
if (ulen >= lengthof(sbuf))
2233+
uchar = palloc_array(UChar, ulen + 1);
22392234

22402235
ulen = uchar_convert(icu_converter, uchar, ulen + 1, src, srclen);
22412236

@@ -2253,8 +2248,8 @@ pg_strnxfrm_prefix_icu_no_utf8(char *dest, const char *src, int32_t srclen,
22532248
(errmsg("sort key generation failed: %s",
22542249
u_errorName(status))));
22552250

2256-
if (buf != sbuf)
2257-
pfree(buf);
2251+
if (uchar != sbuf)
2252+
pfree(uchar);
22582253

22592254
return result_bsize;
22602255
}
@@ -2599,8 +2594,12 @@ init_icu_converter(void)
25992594

26002595
/*
26012596
* Find length, in UChars, of given string if converted to UChar string.
2597+
*
2598+
* Note: given the assumption that the input string fits in MaxAllocSize,
2599+
* the result cannot overflow int32_t. But callers must be careful about
2600+
* multiplying the result by sizeof(UChar).
26022601
*/
2603-
static size_t
2602+
static int32_t
26042603
uchar_length(UConverter *converter, const char *str, int32_t len)
26052604
{
26062605
UErrorCode status = U_ZERO_ERROR;
@@ -2624,7 +2623,6 @@ uchar_convert(UConverter *converter, UChar *dest, int32_t destlen,
26242623
UErrorCode status = U_ZERO_ERROR;
26252624
int32_t ulen;
26262625

2627-
status = U_ZERO_ERROR;
26282626
ulen = ucnv_toUChars(converter, dest, destlen, src, srclen, &status);
26292627
if (U_FAILURE(status))
26302628
ereport(ERROR,
@@ -2653,7 +2651,7 @@ icu_to_uchar(UChar **buff_uchar, const char *buff, size_t nbytes)
26532651

26542652
len_uchar = uchar_length(icu_converter, buff, nbytes);
26552653

2656-
*buff_uchar = palloc((len_uchar + 1) * sizeof(**buff_uchar));
2654+
*buff_uchar = palloc_array(UChar, len_uchar + 1);
26572655
len_uchar = uchar_convert(icu_converter,
26582656
*buff_uchar, len_uchar + 1, buff, nbytes);
26592657

0 commit comments

Comments
 (0)