Skip to content

Commit 9142156

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 86cbe9e commit 9142156

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

@@ -994,7 +995,7 @@ parse_comma_separated_list(char **startptr, bool *more)
994995
char *p;
995996
char *s = *startptr;
996997
char *e;
997-
int len;
998+
size_t len;
998999

9991000
/*
10001001
* Search for the end of the current element; a comma or end-of-string
@@ -5081,7 +5082,21 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
50815082
/* concatenate values into a single string with newline terminators */
50825083
size = 1; /* for the trailing null */
50835084
for (i = 0; values[i] != NULL; i++)
5085+
{
5086+
if (values[i]->bv_len >= INT_MAX ||
5087+
size > (INT_MAX - (values[i]->bv_len + 1)))
5088+
{
5089+
appendPQExpBuffer(errorMessage,
5090+
libpq_gettext("connection info string size exceeds the maximum allowed (%d)\n"),
5091+
INT_MAX);
5092+
ldap_value_free_len(values);
5093+
ldap_unbind(ld);
5094+
return 3;
5095+
}
5096+
50845097
size += values[i]->bv_len + 1;
5098+
}
5099+
50855100
if ((result = malloc(size)) == NULL)
50865101
{
50875102
appendPQExpBufferStr(errorMessage,

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;
@@ -1259,7 +1264,7 @@ pqRowProcessor(PGconn *conn, const char **errmsgp)
12591264
bool isbinary = (res->attDescs[i].format != 0);
12601265
char *val;
12611266

1262-
val = (char *) pqResultAlloc(res, clen + 1, isbinary);
1267+
val = (char *) pqResultAlloc(res, (size_t) clen + 1, isbinary);
12631268
if (val == NULL)
12641269
goto fail;
12651270

@@ -4108,6 +4113,27 @@ PQescapeString(char *to, const char *from, size_t length)
41084113
}
41094114

41104115

4116+
/*
4117+
* Frontend version of the backend's add_size(), intended to be API-compatible
4118+
* with the pg_add_*_overflow() helpers. Stores the result into *dst on success.
4119+
* Returns true instead if the addition overflows.
4120+
*
4121+
* TODO: move to common/int.h
4122+
*/
4123+
static bool
4124+
add_size_overflow(size_t s1, size_t s2, size_t *dst)
4125+
{
4126+
size_t result;
4127+
4128+
result = s1 + s2;
4129+
if (result < s1 || result < s2)
4130+
return true;
4131+
4132+
*dst = result;
4133+
return false;
4134+
}
4135+
4136+
41114137
/*
41124138
* Escape arbitrary strings. If as_ident is true, we escape the result
41134139
* as an identifier; if false, as a literal. The result is returned in
@@ -4120,8 +4146,8 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
41204146
const char *s;
41214147
char *result;
41224148
char *rp;
4123-
int num_quotes = 0; /* single or double, depending on as_ident */
4124-
int num_backslashes = 0;
4149+
size_t num_quotes = 0; /* single or double, depending on as_ident */
4150+
size_t num_backslashes = 0;
41254151
size_t input_len = strnlen(str, len);
41264152
size_t result_size;
41274153
char quote_char = as_ident ? '"' : '\'';
@@ -4188,10 +4214,21 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
41884214
}
41894215
}
41904216

4191-
/* Allocate output buffer. */
4192-
result_size = input_len + num_quotes + 3; /* two quotes, plus a NUL */
4217+
/*
4218+
* Allocate output buffer. Protect against overflow, in case the caller
4219+
* has allocated a large fraction of the available size_t.
4220+
*/
4221+
if (add_size_overflow(input_len, num_quotes, &result_size) ||
4222+
add_size_overflow(result_size, 3, &result_size)) /* two quotes plus a NUL */
4223+
goto overflow;
4224+
41934225
if (!as_ident && num_backslashes > 0)
4194-
result_size += num_backslashes + 2;
4226+
{
4227+
if (add_size_overflow(result_size, num_backslashes, &result_size) ||
4228+
add_size_overflow(result_size, 2, &result_size)) /* for " E" prefix */
4229+
goto overflow;
4230+
}
4231+
41954232
result = rp = (char *) malloc(result_size);
41964233
if (rp == NULL)
41974234
{
@@ -4265,6 +4302,12 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
42654302
*rp = '\0';
42664303

42674304
return result;
4305+
4306+
overflow:
4307+
appendPQExpBuffer(&conn->errorMessage,
4308+
libpq_gettext("escaped string size exceeds the maximum allowed (%zu)\n"),
4309+
SIZE_MAX);
4310+
return NULL;
42684311
}
42694312

42704313
char *
@@ -4330,30 +4373,51 @@ PQescapeByteaInternal(PGconn *conn,
43304373
unsigned char *result;
43314374
size_t i;
43324375
size_t len;
4333-
size_t bslash_len = (std_strings ? 1 : 2);
4376+
const size_t bslash_len = (std_strings ? 1 : 2);
43344377

43354378
/*
4336-
* empty string has 1 char ('\0')
4379+
* Calculate the escaped length, watching for overflow as we do with
4380+
* PQescapeInternal(). The following code relies on a small constant
4381+
* bslash_len so that small additions and multiplications don't need their
4382+
* own overflow checks.
4383+
*
4384+
* Start with the empty string, which has 1 char ('\0').
43374385
*/
43384386
len = 1;
43394387

43404388
if (use_hex)
43414389
{
4342-
len += bslash_len + 1 + 2 * from_length;
4390+
/* We prepend "\x" and double each input character. */
4391+
if (add_size_overflow(len, bslash_len + 1, &len) ||
4392+
add_size_overflow(len, from_length, &len) ||
4393+
add_size_overflow(len, from_length, &len))
4394+
goto overflow;
43434395
}
43444396
else
43454397
{
43464398
vp = from;
43474399
for (i = from_length; i > 0; i--, vp++)
43484400
{
43494401
if (*vp < 0x20 || *vp > 0x7e)
4350-
len += bslash_len + 3;
4402+
{
4403+
if (add_size_overflow(len, bslash_len + 3, &len)) /* octal "\ooo" */
4404+
goto overflow;
4405+
}
43514406
else if (*vp == '\'')
4352-
len += 2;
4407+
{
4408+
if (add_size_overflow(len, 2, &len)) /* double each quote */
4409+
goto overflow;
4410+
}
43534411
else if (*vp == '\\')
4354-
len += bslash_len + bslash_len;
4412+
{
4413+
if (add_size_overflow(len, bslash_len * 2, &len)) /* double each backslash */
4414+
goto overflow;
4415+
}
43554416
else
4356-
len++;
4417+
{
4418+
if (add_size_overflow(len, 1, &len))
4419+
goto overflow;
4420+
}
43574421
}
43584422
}
43594423

@@ -4415,6 +4479,13 @@ PQescapeByteaInternal(PGconn *conn,
44154479
*rp = '\0';
44164480

44174481
return result;
4482+
4483+
overflow:
4484+
if (conn)
4485+
appendPQExpBuffer(&conn->errorMessage,
4486+
libpq_gettext("escaped bytea size exceeds the maximum allowed (%zu)\n"),
4487+
SIZE_MAX);
4488+
return NULL;
44184489
}
44194490

44204491
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)