Skip to content

Commit 7eb8fca

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 <[email protected]> Reviewed-by: Álvaro Herrera <[email protected]> Security: CVE-2025-12818 Backpatch-through: 13
1 parent 292b81a commit 7eb8fca

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>
@@ -1135,7 +1136,7 @@ parse_comma_separated_list(char **startptr, bool *more)
11351136
char *p;
11361137
char *s = *startptr;
11371138
char *e;
1138-
int len;
1139+
size_t len;
11391140

11401141
/*
11411142
* Search for the end of the current element; a comma or end-of-string
@@ -5744,7 +5745,21 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
57445745
/* concatenate values into a single string with newline terminators */
57455746
size = 1; /* for the trailing null */
57465747
for (i = 0; values[i] != NULL; i++)
5748+
{
5749+
if (values[i]->bv_len >= INT_MAX ||
5750+
size > (INT_MAX - (values[i]->bv_len + 1)))
5751+
{
5752+
libpq_append_error(errorMessage,
5753+
"connection info string size exceeds the maximum allowed (%d)",
5754+
INT_MAX);
5755+
ldap_value_free_len(values);
5756+
ldap_unbind(ld);
5757+
return 3;
5758+
}
5759+
57475760
size += values[i]->bv_len + 1;
5761+
}
5762+
57485763
if ((result = malloc(size)) == NULL)
57495764
{
57505765
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
@@ -511,7 +511,7 @@ PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len)
511511
}
512512
else
513513
{
514-
attval->value = (char *) pqResultAlloc(res, len + 1, true);
514+
attval->value = (char *) pqResultAlloc(res, (size_t) len + 1, true);
515515
if (!attval->value)
516516
goto fail;
517517
attval->len = len;
@@ -603,8 +603,13 @@ pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary)
603603
*/
604604
if (nBytes >= PGRESULT_SEP_ALLOC_THRESHOLD)
605605
{
606-
size_t alloc_size = nBytes + PGRESULT_BLOCK_OVERHEAD;
606+
size_t alloc_size;
607607

608+
/* Don't wrap around with overly large requests. */
609+
if (nBytes > SIZE_MAX - PGRESULT_BLOCK_OVERHEAD)
610+
return NULL;
611+
612+
alloc_size = nBytes + PGRESULT_BLOCK_OVERHEAD;
608613
block = (PGresult_data *) malloc(alloc_size);
609614
if (!block)
610615
return NULL;
@@ -1274,7 +1279,7 @@ pqRowProcessor(PGconn *conn, const char **errmsgp)
12741279
bool isbinary = (res->attDescs[i].format != 0);
12751280
char *val;
12761281

1277-
val = (char *) pqResultAlloc(res, clen + 1, isbinary);
1282+
val = (char *) pqResultAlloc(res, (size_t) clen + 1, isbinary);
12781283
if (val == NULL)
12791284
return 0;
12801285

@@ -4215,6 +4220,27 @@ PQescapeString(char *to, const char *from, size_t length)
42154220
}
42164221

42174222

4223+
/*
4224+
* Frontend version of the backend's add_size(), intended to be API-compatible
4225+
* with the pg_add_*_overflow() helpers. Stores the result into *dst on success.
4226+
* Returns true instead if the addition overflows.
4227+
*
4228+
* TODO: move to common/int.h
4229+
*/
4230+
static bool
4231+
add_size_overflow(size_t s1, size_t s2, size_t *dst)
4232+
{
4233+
size_t result;
4234+
4235+
result = s1 + s2;
4236+
if (result < s1 || result < s2)
4237+
return true;
4238+
4239+
*dst = result;
4240+
return false;
4241+
}
4242+
4243+
42184244
/*
42194245
* Escape arbitrary strings. If as_ident is true, we escape the result
42204246
* as an identifier; if false, as a literal. The result is returned in
@@ -4227,8 +4253,8 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
42274253
const char *s;
42284254
char *result;
42294255
char *rp;
4230-
int num_quotes = 0; /* single or double, depending on as_ident */
4231-
int num_backslashes = 0;
4256+
size_t num_quotes = 0; /* single or double, depending on as_ident */
4257+
size_t num_backslashes = 0;
42324258
size_t input_len = strnlen(str, len);
42334259
size_t result_size;
42344260
char quote_char = as_ident ? '"' : '\'';
@@ -4294,10 +4320,21 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
42944320
}
42954321
}
42964322

4297-
/* Allocate output buffer. */
4298-
result_size = input_len + num_quotes + 3; /* two quotes, plus a NUL */
4323+
/*
4324+
* Allocate output buffer. Protect against overflow, in case the caller
4325+
* has allocated a large fraction of the available size_t.
4326+
*/
4327+
if (add_size_overflow(input_len, num_quotes, &result_size) ||
4328+
add_size_overflow(result_size, 3, &result_size)) /* two quotes plus a NUL */
4329+
goto overflow;
4330+
42994331
if (!as_ident && num_backslashes > 0)
4300-
result_size += num_backslashes + 2;
4332+
{
4333+
if (add_size_overflow(result_size, num_backslashes, &result_size) ||
4334+
add_size_overflow(result_size, 2, &result_size)) /* for " E" prefix */
4335+
goto overflow;
4336+
}
4337+
43014338
result = rp = (char *) malloc(result_size);
43024339
if (rp == NULL)
43034340
{
@@ -4370,6 +4407,12 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
43704407
*rp = '\0';
43714408

43724409
return result;
4410+
4411+
overflow:
4412+
libpq_append_conn_error(conn,
4413+
"escaped string size exceeds the maximum allowed (%zu)",
4414+
SIZE_MAX);
4415+
return NULL;
43734416
}
43744417

43754418
char *
@@ -4435,30 +4478,51 @@ PQescapeByteaInternal(PGconn *conn,
44354478
unsigned char *result;
44364479
size_t i;
44374480
size_t len;
4438-
size_t bslash_len = (std_strings ? 1 : 2);
4481+
const size_t bslash_len = (std_strings ? 1 : 2);
44394482

44404483
/*
4441-
* empty string has 1 char ('\0')
4484+
* Calculate the escaped length, watching for overflow as we do with
4485+
* PQescapeInternal(). The following code relies on a small constant
4486+
* bslash_len so that small additions and multiplications don't need their
4487+
* own overflow checks.
4488+
*
4489+
* Start with the empty string, which has 1 char ('\0').
44424490
*/
44434491
len = 1;
44444492

44454493
if (use_hex)
44464494
{
4447-
len += bslash_len + 1 + 2 * from_length;
4495+
/* We prepend "\x" and double each input character. */
4496+
if (add_size_overflow(len, bslash_len + 1, &len) ||
4497+
add_size_overflow(len, from_length, &len) ||
4498+
add_size_overflow(len, from_length, &len))
4499+
goto overflow;
44484500
}
44494501
else
44504502
{
44514503
vp = from;
44524504
for (i = from_length; i > 0; i--, vp++)
44534505
{
44544506
if (*vp < 0x20 || *vp > 0x7e)
4455-
len += bslash_len + 3;
4507+
{
4508+
if (add_size_overflow(len, bslash_len + 3, &len)) /* octal "\ooo" */
4509+
goto overflow;
4510+
}
44564511
else if (*vp == '\'')
4457-
len += 2;
4512+
{
4513+
if (add_size_overflow(len, 2, &len)) /* double each quote */
4514+
goto overflow;
4515+
}
44584516
else if (*vp == '\\')
4459-
len += bslash_len + bslash_len;
4517+
{
4518+
if (add_size_overflow(len, bslash_len * 2, &len)) /* double each backslash */
4519+
goto overflow;
4520+
}
44604521
else
4461-
len++;
4522+
{
4523+
if (add_size_overflow(len, 1, &len))
4524+
goto overflow;
4525+
}
44624526
}
44634527
}
44644528

@@ -4519,6 +4583,13 @@ PQescapeByteaInternal(PGconn *conn,
45194583
*rp = '\0';
45204584

45214585
return result;
4586+
4587+
overflow:
4588+
if (conn)
4589+
libpq_append_conn_error(conn,
4590+
"escaped bytea size exceeds the maximum allowed (%zu)",
4591+
SIZE_MAX);
4592+
return NULL;
45224593
}
45234594

45244595
unsigned char *

src/interfaces/libpq/fe-print.c

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

107+
/*
108+
* Quick sanity check on po->fieldSep, since we make heavy use of int
109+
* math throughout.
110+
*/
111+
if (fs_len < strlen(po->fieldSep))
112+
{
113+
fprintf(stderr, libpq_gettext("overlong field separator\n"));
114+
goto exit;
115+
}
116+
107117
nTups = PQntuples(res);
108118
fieldNames = (const char **) calloc(nFields, sizeof(char *));
109119
fieldNotNum = (unsigned char *) calloc(nFields, 1);
@@ -391,7 +401,7 @@ do_field(const PQprintOpt *po, const PGresult *res,
391401
{
392402
if (plen > fieldMax[j])
393403
fieldMax[j] = plen;
394-
if (!(fields[i * nFields + j] = (char *) malloc(plen + 1)))
404+
if (!(fields[i * nFields + j] = (char *) malloc((size_t) plen + 1)))
395405
{
396406
fprintf(stderr, libpq_gettext("out of memory\n"));
397407
return false;
@@ -441,6 +451,27 @@ do_field(const PQprintOpt *po, const PGresult *res,
441451
}
442452

443453

454+
/*
455+
* Frontend version of the backend's add_size(), intended to be API-compatible
456+
* with the pg_add_*_overflow() helpers. Stores the result into *dst on success.
457+
* Returns true instead if the addition overflows.
458+
*
459+
* TODO: move to common/int.h
460+
*/
461+
static bool
462+
add_size_overflow(size_t s1, size_t s2, size_t *dst)
463+
{
464+
size_t result;
465+
466+
result = s1 + s2;
467+
if (result < s1 || result < s2)
468+
return true;
469+
470+
*dst = result;
471+
return false;
472+
}
473+
474+
444475
static char *
445476
do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax,
446477
const char **fieldNames, unsigned char *fieldNotNum,
@@ -453,15 +484,31 @@ do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax,
453484
fputs("<tr>", fout);
454485
else
455486
{
456-
int tot = 0;
487+
size_t tot = 0;
457488
int n = 0;
458489
char *p = NULL;
459490

491+
/* Calculate the border size, checking for overflow. */
460492
for (; n < nFields; n++)
461-
tot += fieldMax[n] + fs_len + (po->standard ? 2 : 0);
493+
{
494+
/* Field plus separator, plus 2 extra '-' in standard format. */
495+
if (add_size_overflow(tot, fieldMax[n], &tot) ||
496+
add_size_overflow(tot, fs_len, &tot) ||
497+
(po->standard && add_size_overflow(tot, 2, &tot)))
498+
goto overflow;
499+
}
462500
if (po->standard)
463-
tot += fs_len * 2 + 2;
464-
border = malloc(tot + 1);
501+
{
502+
/* An extra separator at the front and back. */
503+
if (add_size_overflow(tot, fs_len, &tot) ||
504+
add_size_overflow(tot, fs_len, &tot) ||
505+
add_size_overflow(tot, 2, &tot))
506+
goto overflow;
507+
}
508+
if (add_size_overflow(tot, 1, &tot)) /* terminator */
509+
goto overflow;
510+
511+
border = malloc(tot);
465512
if (!border)
466513
{
467514
fprintf(stderr, libpq_gettext("out of memory\n"));
@@ -524,6 +571,10 @@ do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax,
524571
else
525572
fprintf(fout, "\n%s\n", border);
526573
return border;
574+
575+
overflow:
576+
fprintf(stderr, libpq_gettext("header size exceeds the maximum allowed\n"));
577+
return NULL;
527578
}
528579

529580

0 commit comments

Comments
 (0)