Skip to content

Commit 96d2c7e

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 e792be6 commit 96d2c7e

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 <time.h>
2223
#include <unistd.h>
2324

@@ -998,7 +999,7 @@ parse_comma_separated_list(char **startptr, bool *more)
998999
char *p;
9991000
char *s = *startptr;
10001001
char *e;
1001-
int len;
1002+
size_t len;
10021003

10031004
/*
10041005
* Search for the end of the current element; a comma or end-of-string
@@ -4968,7 +4969,21 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
49684969
/* concatenate values into a single string with newline terminators */
49694970
size = 1; /* for the trailing null */
49704971
for (i = 0; values[i] != NULL; i++)
4972+
{
4973+
if (values[i]->bv_len >= INT_MAX ||
4974+
size > (INT_MAX - (values[i]->bv_len + 1)))
4975+
{
4976+
appendPQExpBuffer(errorMessage,
4977+
libpq_gettext("connection info string size exceeds the maximum allowed (%d)\n"),
4978+
INT_MAX);
4979+
ldap_value_free_len(values);
4980+
ldap_unbind(ld);
4981+
return 3;
4982+
}
4983+
49714984
size += values[i]->bv_len + 1;
4985+
}
4986+
49724987
if ((result = malloc(size)) == NULL)
49734988
{
49744989
appendPQExpBufferStr(errorMessage,

src/interfaces/libpq/fe-exec.c

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len)
491491
}
492492
else
493493
{
494-
attval->value = (char *) pqResultAlloc(res, len + 1, true);
494+
attval->value = (char *) pqResultAlloc(res, (size_t) len + 1, true);
495495
if (!attval->value)
496496
goto fail;
497497
attval->len = len;
@@ -579,8 +579,13 @@ pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary)
579579
*/
580580
if (nBytes >= PGRESULT_SEP_ALLOC_THRESHOLD)
581581
{
582-
size_t alloc_size = nBytes + PGRESULT_BLOCK_OVERHEAD;
582+
size_t alloc_size;
583583

584+
/* Don't wrap around with overly large requests. */
585+
if (nBytes > SIZE_MAX - PGRESULT_BLOCK_OVERHEAD)
586+
return NULL;
587+
588+
alloc_size = nBytes + PGRESULT_BLOCK_OVERHEAD;
584589
block = (PGresult_data *) malloc(alloc_size);
585590
if (!block)
586591
return NULL;
@@ -1156,7 +1161,7 @@ pqRowProcessor(PGconn *conn, const char **errmsgp)
11561161
bool isbinary = (res->attDescs[i].format != 0);
11571162
char *val;
11581163

1159-
val = (char *) pqResultAlloc(res, clen + 1, isbinary);
1164+
val = (char *) pqResultAlloc(res, (size_t) clen + 1, isbinary);
11601165
if (val == NULL)
11611166
goto fail;
11621167

@@ -4084,6 +4089,27 @@ PQescapeString(char *to, const char *from, size_t length)
40844089
}
40854090

40864091

4092+
/*
4093+
* Frontend version of the backend's add_size(), intended to be API-compatible
4094+
* with the pg_add_*_overflow() helpers. Stores the result into *dst on success.
4095+
* Returns true instead if the addition overflows.
4096+
*
4097+
* TODO: move to common/int.h
4098+
*/
4099+
static bool
4100+
add_size_overflow(size_t s1, size_t s2, size_t *dst)
4101+
{
4102+
size_t result;
4103+
4104+
result = s1 + s2;
4105+
if (result < s1 || result < s2)
4106+
return true;
4107+
4108+
*dst = result;
4109+
return false;
4110+
}
4111+
4112+
40874113
/*
40884114
* Escape arbitrary strings. If as_ident is true, we escape the result
40894115
* as an identifier; if false, as a literal. The result is returned in
@@ -4096,8 +4122,8 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
40964122
const char *s;
40974123
char *result;
40984124
char *rp;
4099-
int num_quotes = 0; /* single or double, depending on as_ident */
4100-
int num_backslashes = 0;
4125+
size_t num_quotes = 0; /* single or double, depending on as_ident */
4126+
size_t num_backslashes = 0;
41014127
size_t input_len = strnlen(str, len);
41024128
size_t result_size;
41034129
char quote_char = as_ident ? '"' : '\'';
@@ -4163,10 +4189,21 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
41634189
}
41644190
}
41654191

4166-
/* Allocate output buffer. */
4167-
result_size = input_len + num_quotes + 3; /* two quotes, plus a NUL */
4192+
/*
4193+
* Allocate output buffer. Protect against overflow, in case the caller
4194+
* has allocated a large fraction of the available size_t.
4195+
*/
4196+
if (add_size_overflow(input_len, num_quotes, &result_size) ||
4197+
add_size_overflow(result_size, 3, &result_size)) /* two quotes plus a NUL */
4198+
goto overflow;
4199+
41684200
if (!as_ident && num_backslashes > 0)
4169-
result_size += num_backslashes + 2;
4201+
{
4202+
if (add_size_overflow(result_size, num_backslashes, &result_size) ||
4203+
add_size_overflow(result_size, 2, &result_size)) /* for " E" prefix */
4204+
goto overflow;
4205+
}
4206+
41704207
result = rp = (char *) malloc(result_size);
41714208
if (rp == NULL)
41724209
{
@@ -4240,6 +4277,12 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
42404277
*rp = '\0';
42414278

42424279
return result;
4280+
4281+
overflow:
4282+
appendPQExpBuffer(&conn->errorMessage,
4283+
libpq_gettext("escaped string size exceeds the maximum allowed (%zu)\n"),
4284+
SIZE_MAX);
4285+
return NULL;
42434286
}
42444287

42454288
char *
@@ -4305,30 +4348,51 @@ PQescapeByteaInternal(PGconn *conn,
43054348
unsigned char *result;
43064349
size_t i;
43074350
size_t len;
4308-
size_t bslash_len = (std_strings ? 1 : 2);
4351+
const size_t bslash_len = (std_strings ? 1 : 2);
43094352

43104353
/*
4311-
* empty string has 1 char ('\0')
4354+
* Calculate the escaped length, watching for overflow as we do with
4355+
* PQescapeInternal(). The following code relies on a small constant
4356+
* bslash_len so that small additions and multiplications don't need their
4357+
* own overflow checks.
4358+
*
4359+
* Start with the empty string, which has 1 char ('\0').
43124360
*/
43134361
len = 1;
43144362

43154363
if (use_hex)
43164364
{
4317-
len += bslash_len + 1 + 2 * from_length;
4365+
/* We prepend "\x" and double each input character. */
4366+
if (add_size_overflow(len, bslash_len + 1, &len) ||
4367+
add_size_overflow(len, from_length, &len) ||
4368+
add_size_overflow(len, from_length, &len))
4369+
goto overflow;
43184370
}
43194371
else
43204372
{
43214373
vp = from;
43224374
for (i = from_length; i > 0; i--, vp++)
43234375
{
43244376
if (*vp < 0x20 || *vp > 0x7e)
4325-
len += bslash_len + 3;
4377+
{
4378+
if (add_size_overflow(len, bslash_len + 3, &len)) /* octal "\ooo" */
4379+
goto overflow;
4380+
}
43264381
else if (*vp == '\'')
4327-
len += 2;
4382+
{
4383+
if (add_size_overflow(len, 2, &len)) /* double each quote */
4384+
goto overflow;
4385+
}
43284386
else if (*vp == '\\')
4329-
len += bslash_len + bslash_len;
4387+
{
4388+
if (add_size_overflow(len, bslash_len * 2, &len)) /* double each backslash */
4389+
goto overflow;
4390+
}
43304391
else
4331-
len++;
4392+
{
4393+
if (add_size_overflow(len, 1, &len))
4394+
goto overflow;
4395+
}
43324396
}
43334397
}
43344398

@@ -4390,6 +4454,13 @@ PQescapeByteaInternal(PGconn *conn,
43904454
*rp = '\0';
43914455

43924456
return result;
4457+
4458+
overflow:
4459+
if (conn)
4460+
appendPQExpBuffer(&conn->errorMessage,
4461+
libpq_gettext("escaped bytea size exceeds the maximum allowed (%zu)\n"),
4462+
SIZE_MAX);
4463+
return NULL;
43934464
}
43944465

43954466
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);
@@ -408,7 +418,7 @@ do_field(const PQprintOpt *po, const PGresult *res,
408418
{
409419
if (plen > fieldMax[j])
410420
fieldMax[j] = plen;
411-
if (!(fields[i * nFields + j] = (char *) malloc(plen + 1)))
421+
if (!(fields[i * nFields + j] = (char *) malloc((size_t) plen + 1)))
412422
{
413423
fprintf(stderr, libpq_gettext("out of memory\n"));
414424
return false;
@@ -458,6 +468,27 @@ do_field(const PQprintOpt *po, const PGresult *res,
458468
}
459469

460470

471+
/*
472+
* Frontend version of the backend's add_size(), intended to be API-compatible
473+
* with the pg_add_*_overflow() helpers. Stores the result into *dst on success.
474+
* Returns true instead if the addition overflows.
475+
*
476+
* TODO: move to common/int.h
477+
*/
478+
static bool
479+
add_size_overflow(size_t s1, size_t s2, size_t *dst)
480+
{
481+
size_t result;
482+
483+
result = s1 + s2;
484+
if (result < s1 || result < s2)
485+
return true;
486+
487+
*dst = result;
488+
return false;
489+
}
490+
491+
461492
static char *
462493
do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax,
463494
const char **fieldNames, unsigned char *fieldNotNum,
@@ -470,15 +501,31 @@ do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax,
470501
fputs("<tr>", fout);
471502
else
472503
{
473-
int tot = 0;
504+
size_t tot = 0;
474505
int n = 0;
475506
char *p = NULL;
476507

508+
/* Calculate the border size, checking for overflow. */
477509
for (; n < nFields; n++)
478-
tot += fieldMax[n] + fs_len + (po->standard ? 2 : 0);
510+
{
511+
/* Field plus separator, plus 2 extra '-' in standard format. */
512+
if (add_size_overflow(tot, fieldMax[n], &tot) ||
513+
add_size_overflow(tot, fs_len, &tot) ||
514+
(po->standard && add_size_overflow(tot, 2, &tot)))
515+
goto overflow;
516+
}
479517
if (po->standard)
480-
tot += fs_len * 2 + 2;
481-
border = malloc(tot + 1);
518+
{
519+
/* An extra separator at the front and back. */
520+
if (add_size_overflow(tot, fs_len, &tot) ||
521+
add_size_overflow(tot, fs_len, &tot) ||
522+
add_size_overflow(tot, 2, &tot))
523+
goto overflow;
524+
}
525+
if (add_size_overflow(tot, 1, &tot)) /* terminator */
526+
goto overflow;
527+
528+
border = malloc(tot);
482529
if (!border)
483530
{
484531
fprintf(stderr, libpq_gettext("out of memory\n"));
@@ -541,6 +588,10 @@ do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax,
541588
else
542589
fprintf(fout, "\n%s\n", border);
543590
return border;
591+
592+
overflow:
593+
fprintf(stderr, libpq_gettext("header size exceeds the maximum allowed\n"));
594+
return NULL;
544595
}
545596

546597

0 commit comments

Comments
 (0)