Skip to content

Commit 585fd9b

Browse files
committed
libpq: Prevent some overflows of int/size_t
Several functions could overflow their size calculations, when presented with very large inputs from remote and/or untrusted locations, and then allocate buffers that were too small to hold the intended contents. Switch from int to size_t where appropriate, and check for overflow conditions when the inputs could have plausibly originated outside of the libpq trust boundary. (Overflows from within the trust boundary are still possible, but these will be fixed separately.) A version of add_size() is ported from the backend to assist with code that performs more complicated concatenation. Reported-by: Aleksey Solovev (Positive Technologies) Reviewed-by: Noah Misch <noah@leadboat.com> Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de> Security: CVE-2025-12818 Backpatch-through: 13
1 parent 4536776 commit 585fd9b

File tree

5 files changed

+224
-33
lines changed

5 files changed

+224
-33
lines changed

src/interfaces/libpq/fe-connect.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <sys/stat.h>
1919
#include <fcntl.h>
2020
#include <ctype.h>
21+
#include <limits.h>
2122
#include <netdb.h>
2223
#include <time.h>
2324
#include <unistd.h>
@@ -1010,7 +1011,7 @@ parse_comma_separated_list(char **startptr, bool *more)
10101011
char *p;
10111012
char *s = *startptr;
10121013
char *e;
1013-
int len;
1014+
size_t len;
10141015

10151016
/*
10161017
* Search for the end of the current element; a comma or end-of-string
@@ -5406,7 +5407,21 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
54065407
/* concatenate values into a single string with newline terminators */
54075408
size = 1; /* for the trailing null */
54085409
for (i = 0; values[i] != NULL; i++)
5410+
{
5411+
if (values[i]->bv_len >= INT_MAX ||
5412+
size > (INT_MAX - (values[i]->bv_len + 1)))
5413+
{
5414+
libpq_append_error(errorMessage,
5415+
"connection info string size exceeds the maximum allowed (%d)",
5416+
INT_MAX);
5417+
ldap_value_free_len(values);
5418+
ldap_unbind(ld);
5419+
return 3;
5420+
}
5421+
54095422
size += values[i]->bv_len + 1;
5423+
}
5424+
54105425
if ((result = malloc(size)) == NULL)
54115426
{
54125427
libpq_append_error(errorMessage, "out of memory");

src/interfaces/libpq/fe-exec.c

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len)
508508
}
509509
else
510510
{
511-
attval->value = (char *) pqResultAlloc(res, len + 1, true);
511+
attval->value = (char *) pqResultAlloc(res, (size_t) len + 1, true);
512512
if (!attval->value)
513513
goto fail;
514514
attval->len = len;
@@ -600,8 +600,13 @@ pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary)
600600
*/
601601
if (nBytes >= PGRESULT_SEP_ALLOC_THRESHOLD)
602602
{
603-
size_t alloc_size = nBytes + PGRESULT_BLOCK_OVERHEAD;
603+
size_t alloc_size;
604604

605+
/* Don't wrap around with overly large requests. */
606+
if (nBytes > SIZE_MAX - PGRESULT_BLOCK_OVERHEAD)
607+
return NULL;
608+
609+
alloc_size = nBytes + PGRESULT_BLOCK_OVERHEAD;
605610
block = (PGresult_data *) malloc(alloc_size);
606611
if (!block)
607612
return NULL;
@@ -1257,7 +1262,7 @@ pqRowProcessor(PGconn *conn, const char **errmsgp)
12571262
bool isbinary = (res->attDescs[i].format != 0);
12581263
char *val;
12591264

1260-
val = (char *) pqResultAlloc(res, clen + 1, isbinary);
1265+
val = (char *) pqResultAlloc(res, (size_t) clen + 1, isbinary);
12611266
if (val == NULL)
12621267
goto fail;
12631268

@@ -4070,6 +4075,27 @@ PQescapeString(char *to, const char *from, size_t length)
40704075
}
40714076

40724077

4078+
/*
4079+
* Frontend version of the backend's add_size(), intended to be API-compatible
4080+
* with the pg_add_*_overflow() helpers. Stores the result into *dst on success.
4081+
* Returns true instead if the addition overflows.
4082+
*
4083+
* TODO: move to common/int.h
4084+
*/
4085+
static bool
4086+
add_size_overflow(size_t s1, size_t s2, size_t *dst)
4087+
{
4088+
size_t result;
4089+
4090+
result = s1 + s2;
4091+
if (result < s1 || result < s2)
4092+
return true;
4093+
4094+
*dst = result;
4095+
return false;
4096+
}
4097+
4098+
40734099
/*
40744100
* Escape arbitrary strings. If as_ident is true, we escape the result
40754101
* as an identifier; if false, as a literal. The result is returned in
@@ -4082,8 +4108,8 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
40824108
const char *s;
40834109
char *result;
40844110
char *rp;
4085-
int num_quotes = 0; /* single or double, depending on as_ident */
4086-
int num_backslashes = 0;
4111+
size_t num_quotes = 0; /* single or double, depending on as_ident */
4112+
size_t num_backslashes = 0;
40874113
size_t input_len = strnlen(str, len);
40884114
size_t result_size;
40894115
char quote_char = as_ident ? '"' : '\'';
@@ -4149,10 +4175,21 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
41494175
}
41504176
}
41514177

4152-
/* Allocate output buffer. */
4153-
result_size = input_len + num_quotes + 3; /* two quotes, plus a NUL */
4178+
/*
4179+
* Allocate output buffer. Protect against overflow, in case the caller
4180+
* has allocated a large fraction of the available size_t.
4181+
*/
4182+
if (add_size_overflow(input_len, num_quotes, &result_size) ||
4183+
add_size_overflow(result_size, 3, &result_size)) /* two quotes plus a NUL */
4184+
goto overflow;
4185+
41544186
if (!as_ident && num_backslashes > 0)
4155-
result_size += num_backslashes + 2;
4187+
{
4188+
if (add_size_overflow(result_size, num_backslashes, &result_size) ||
4189+
add_size_overflow(result_size, 2, &result_size)) /* for " E" prefix */
4190+
goto overflow;
4191+
}
4192+
41564193
result = rp = (char *) malloc(result_size);
41574194
if (rp == NULL)
41584195
{
@@ -4225,6 +4262,12 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
42254262
*rp = '\0';
42264263

42274264
return result;
4265+
4266+
overflow:
4267+
libpq_append_conn_error(conn,
4268+
"escaped string size exceeds the maximum allowed (%zu)",
4269+
SIZE_MAX);
4270+
return NULL;
42284271
}
42294272

42304273
char *
@@ -4290,30 +4333,51 @@ PQescapeByteaInternal(PGconn *conn,
42904333
unsigned char *result;
42914334
size_t i;
42924335
size_t len;
4293-
size_t bslash_len = (std_strings ? 1 : 2);
4336+
const size_t bslash_len = (std_strings ? 1 : 2);
42944337

42954338
/*
4296-
* empty string has 1 char ('\0')
4339+
* Calculate the escaped length, watching for overflow as we do with
4340+
* PQescapeInternal(). The following code relies on a small constant
4341+
* bslash_len so that small additions and multiplications don't need their
4342+
* own overflow checks.
4343+
*
4344+
* Start with the empty string, which has 1 char ('\0').
42974345
*/
42984346
len = 1;
42994347

43004348
if (use_hex)
43014349
{
4302-
len += bslash_len + 1 + 2 * from_length;
4350+
/* We prepend "\x" and double each input character. */
4351+
if (add_size_overflow(len, bslash_len + 1, &len) ||
4352+
add_size_overflow(len, from_length, &len) ||
4353+
add_size_overflow(len, from_length, &len))
4354+
goto overflow;
43034355
}
43044356
else
43054357
{
43064358
vp = from;
43074359
for (i = from_length; i > 0; i--, vp++)
43084360
{
43094361
if (*vp < 0x20 || *vp > 0x7e)
4310-
len += bslash_len + 3;
4362+
{
4363+
if (add_size_overflow(len, bslash_len + 3, &len)) /* octal "\ooo" */
4364+
goto overflow;
4365+
}
43114366
else if (*vp == '\'')
4312-
len += 2;
4367+
{
4368+
if (add_size_overflow(len, 2, &len)) /* double each quote */
4369+
goto overflow;
4370+
}
43134371
else if (*vp == '\\')
4314-
len += bslash_len + bslash_len;
4372+
{
4373+
if (add_size_overflow(len, bslash_len * 2, &len)) /* double each backslash */
4374+
goto overflow;
4375+
}
43154376
else
4316-
len++;
4377+
{
4378+
if (add_size_overflow(len, 1, &len))
4379+
goto overflow;
4380+
}
43174381
}
43184382
}
43194383

@@ -4374,6 +4438,13 @@ PQescapeByteaInternal(PGconn *conn,
43744438
*rp = '\0';
43754439

43764440
return result;
4441+
4442+
overflow:
4443+
if (conn)
4444+
libpq_append_conn_error(conn,
4445+
"escaped bytea size exceeds the maximum allowed (%zu)",
4446+
SIZE_MAX);
4447+
return NULL;
43774448
}
43784449

43794450
unsigned char *

src/interfaces/libpq/fe-print.c

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ PQprint(FILE *fout, const PGresult *res, const PQprintOpt *po)
107107
} screen_size;
108108
#endif
109109

110+
/*
111+
* Quick sanity check on po->fieldSep, since we make heavy use of int
112+
* math throughout.
113+
*/
114+
if (fs_len < strlen(po->fieldSep))
115+
{
116+
fprintf(stderr, libpq_gettext("overlong field separator\n"));
117+
goto exit;
118+
}
119+
110120
nTups = PQntuples(res);
111121
fieldNames = (const char **) calloc(nFields, sizeof(char *));
112122
fieldNotNum = (unsigned char *) calloc(nFields, 1);
@@ -402,7 +412,7 @@ do_field(const PQprintOpt *po, const PGresult *res,
402412
{
403413
if (plen > fieldMax[j])
404414
fieldMax[j] = plen;
405-
if (!(fields[i * nFields + j] = (char *) malloc(plen + 1)))
415+
if (!(fields[i * nFields + j] = (char *) malloc((size_t) plen + 1)))
406416
{
407417
fprintf(stderr, libpq_gettext("out of memory\n"));
408418
return false;
@@ -452,6 +462,27 @@ do_field(const PQprintOpt *po, const PGresult *res,
452462
}
453463

454464

465+
/*
466+
* Frontend version of the backend's add_size(), intended to be API-compatible
467+
* with the pg_add_*_overflow() helpers. Stores the result into *dst on success.
468+
* Returns true instead if the addition overflows.
469+
*
470+
* TODO: move to common/int.h
471+
*/
472+
static bool
473+
add_size_overflow(size_t s1, size_t s2, size_t *dst)
474+
{
475+
size_t result;
476+
477+
result = s1 + s2;
478+
if (result < s1 || result < s2)
479+
return true;
480+
481+
*dst = result;
482+
return false;
483+
}
484+
485+
455486
static char *
456487
do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax,
457488
const char **fieldNames, unsigned char *fieldNotNum,
@@ -464,15 +495,31 @@ do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax,
464495
fputs("<tr>", fout);
465496
else
466497
{
467-
int tot = 0;
498+
size_t tot = 0;
468499
int n = 0;
469500
char *p = NULL;
470501

502+
/* Calculate the border size, checking for overflow. */
471503
for (; n < nFields; n++)
472-
tot += fieldMax[n] + fs_len + (po->standard ? 2 : 0);
504+
{
505+
/* Field plus separator, plus 2 extra '-' in standard format. */
506+
if (add_size_overflow(tot, fieldMax[n], &tot) ||
507+
add_size_overflow(tot, fs_len, &tot) ||
508+
(po->standard && add_size_overflow(tot, 2, &tot)))
509+
goto overflow;
510+
}
473511
if (po->standard)
474-
tot += fs_len * 2 + 2;
475-
border = malloc(tot + 1);
512+
{
513+
/* An extra separator at the front and back. */
514+
if (add_size_overflow(tot, fs_len, &tot) ||
515+
add_size_overflow(tot, fs_len, &tot) ||
516+
add_size_overflow(tot, 2, &tot))
517+
goto overflow;
518+
}
519+
if (add_size_overflow(tot, 1, &tot)) /* terminator */
520+
goto overflow;
521+
522+
border = malloc(tot);
476523
if (!border)
477524
{
478525
fprintf(stderr, libpq_gettext("out of memory\n"));
@@ -535,6 +582,10 @@ do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax,
535582
else
536583
fprintf(fout, "\n%s\n", border);
537584
return border;
585+
586+
overflow:
587+
fprintf(stderr, libpq_gettext("header size exceeds the maximum allowed\n"));
588+
return NULL;
538589
}
539590

540591

0 commit comments

Comments
 (0)