🌐 AI搜索 & 代理 主页
Skip to content

Commit d6f0c0d

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 498ff77 commit d6f0c0d

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

@@ -979,7 +980,7 @@ parse_comma_separated_list(char **startptr, bool *more)
979980
char *p;
980981
char *s = *startptr;
981982
char *e;
982-
int len;
983+
size_t len;
983984

984985
/*
985986
* Search for the end of the current element; a comma or end-of-string
@@ -4904,7 +4905,21 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
49044905
/* concatenate values into a single string with newline terminators */
49054906
size = 1; /* for the trailing null */
49064907
for (i = 0; values[i] != NULL; i++)
4908+
{
4909+
if (values[i]->bv_len >= INT_MAX ||
4910+
size > (INT_MAX - (values[i]->bv_len + 1)))
4911+
{
4912+
printfPQExpBuffer(errorMessage,
4913+
libpq_gettext("connection info string size exceeds the maximum allowed (%d)\n"),
4914+
INT_MAX);
4915+
ldap_value_free_len(values);
4916+
ldap_unbind(ld);
4917+
return 3;
4918+
}
4919+
49074920
size += values[i]->bv_len + 1;
4921+
}
4922+
49084923
if ((result = malloc(size)) == NULL)
49094924
{
49104925
printfPQExpBuffer(errorMessage,

src/interfaces/libpq/fe-exec.c

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len)
486486
}
487487
else
488488
{
489-
attval->value = (char *) pqResultAlloc(res, len + 1, true);
489+
attval->value = (char *) pqResultAlloc(res, (size_t) len + 1, true);
490490
if (!attval->value)
491491
goto fail;
492492
attval->len = len;
@@ -574,8 +574,13 @@ pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary)
574574
*/
575575
if (nBytes >= PGRESULT_SEP_ALLOC_THRESHOLD)
576576
{
577-
size_t alloc_size = nBytes + PGRESULT_BLOCK_OVERHEAD;
577+
size_t alloc_size;
578578

579+
/* Don't wrap around with overly large requests. */
580+
if (nBytes > SIZE_MAX - PGRESULT_BLOCK_OVERHEAD)
581+
return NULL;
582+
583+
alloc_size = nBytes + PGRESULT_BLOCK_OVERHEAD;
579584
block = (PGresult_data *) malloc(alloc_size);
580585
if (!block)
581586
return NULL;
@@ -1181,7 +1186,7 @@ pqRowProcessor(PGconn *conn, const char **errmsgp)
11811186
bool isbinary = (res->attDescs[i].format != 0);
11821187
char *val;
11831188

1184-
val = (char *) pqResultAlloc(res, clen + 1, isbinary);
1189+
val = (char *) pqResultAlloc(res, (size_t) clen + 1, isbinary);
11851190
if (val == NULL)
11861191
goto fail;
11871192

@@ -3474,6 +3479,27 @@ PQescapeString(char *to, const char *from, size_t length)
34743479
}
34753480

34763481

3482+
/*
3483+
* Frontend version of the backend's add_size(), intended to be API-compatible
3484+
* with the pg_add_*_overflow() helpers. Stores the result into *dst on success.
3485+
* Returns true instead if the addition overflows.
3486+
*
3487+
* TODO: move to common/int.h
3488+
*/
3489+
static bool
3490+
add_size_overflow(size_t s1, size_t s2, size_t *dst)
3491+
{
3492+
size_t result;
3493+
3494+
result = s1 + s2;
3495+
if (result < s1 || result < s2)
3496+
return true;
3497+
3498+
*dst = result;
3499+
return false;
3500+
}
3501+
3502+
34773503
/*
34783504
* Escape arbitrary strings. If as_ident is true, we escape the result
34793505
* as an identifier; if false, as a literal. The result is returned in
@@ -3486,8 +3512,8 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
34863512
const char *s;
34873513
char *result;
34883514
char *rp;
3489-
int num_quotes = 0; /* single or double, depending on as_ident */
3490-
int num_backslashes = 0;
3515+
size_t num_quotes = 0; /* single or double, depending on as_ident */
3516+
size_t num_backslashes = 0;
34913517
size_t input_len = strnlen(str, len);
34923518
size_t result_size;
34933519
char quote_char = as_ident ? '"' : '\'';
@@ -3552,10 +3578,21 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
35523578
}
35533579
}
35543580

3555-
/* Allocate output buffer. */
3556-
result_size = input_len + num_quotes + 3; /* two quotes, plus a NUL */
3581+
/*
3582+
* Allocate output buffer. Protect against overflow, in case the caller
3583+
* has allocated a large fraction of the available size_t.
3584+
*/
3585+
if (add_size_overflow(input_len, num_quotes, &result_size) ||
3586+
add_size_overflow(result_size, 3, &result_size)) /* two quotes plus a NUL */
3587+
goto overflow;
3588+
35573589
if (!as_ident && num_backslashes > 0)
3558-
result_size += num_backslashes + 2;
3590+
{
3591+
if (add_size_overflow(result_size, num_backslashes, &result_size) ||
3592+
add_size_overflow(result_size, 2, &result_size)) /* for " E" prefix */
3593+
goto overflow;
3594+
}
3595+
35593596
result = rp = (char *) malloc(result_size);
35603597
if (rp == NULL)
35613598
{
@@ -3629,6 +3666,12 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
36293666
*rp = '\0';
36303667

36313668
return result;
3669+
3670+
overflow:
3671+
appendPQExpBuffer(&conn->errorMessage,
3672+
libpq_gettext("escaped string size exceeds the maximum allowed (%zu)\n"),
3673+
SIZE_MAX);
3674+
return NULL;
36323675
}
36333676

36343677
char *
@@ -3694,30 +3737,51 @@ PQescapeByteaInternal(PGconn *conn,
36943737
unsigned char *result;
36953738
size_t i;
36963739
size_t len;
3697-
size_t bslash_len = (std_strings ? 1 : 2);
3740+
const size_t bslash_len = (std_strings ? 1 : 2);
36983741

36993742
/*
3700-
* empty string has 1 char ('\0')
3743+
* Calculate the escaped length, watching for overflow as we do with
3744+
* PQescapeInternal(). The following code relies on a small constant
3745+
* bslash_len so that small additions and multiplications don't need their
3746+
* own overflow checks.
3747+
*
3748+
* Start with the empty string, which has 1 char ('\0').
37013749
*/
37023750
len = 1;
37033751

37043752
if (use_hex)
37053753
{
3706-
len += bslash_len + 1 + 2 * from_length;
3754+
/* We prepend "\x" and double each input character. */
3755+
if (add_size_overflow(len, bslash_len + 1, &len) ||
3756+
add_size_overflow(len, from_length, &len) ||
3757+
add_size_overflow(len, from_length, &len))
3758+
goto overflow;
37073759
}
37083760
else
37093761
{
37103762
vp = from;
37113763
for (i = from_length; i > 0; i--, vp++)
37123764
{
37133765
if (*vp < 0x20 || *vp > 0x7e)
3714-
len += bslash_len + 3;
3766+
{
3767+
if (add_size_overflow(len, bslash_len + 3, &len)) /* octal "\ooo" */
3768+
goto overflow;
3769+
}
37153770
else if (*vp == '\'')
3716-
len += 2;
3771+
{
3772+
if (add_size_overflow(len, 2, &len)) /* double each quote */
3773+
goto overflow;
3774+
}
37173775
else if (*vp == '\\')
3718-
len += bslash_len + bslash_len;
3776+
{
3777+
if (add_size_overflow(len, bslash_len * 2, &len)) /* double each backslash */
3778+
goto overflow;
3779+
}
37193780
else
3720-
len++;
3781+
{
3782+
if (add_size_overflow(len, 1, &len))
3783+
goto overflow;
3784+
}
37213785
}
37223786
}
37233787

@@ -3779,6 +3843,13 @@ PQescapeByteaInternal(PGconn *conn,
37793843
*rp = '\0';
37803844

37813845
return result;
3846+
3847+
overflow:
3848+
if (conn)
3849+
appendPQExpBuffer(&conn->errorMessage,
3850+
libpq_gettext("escaped bytea size exceeds the maximum allowed (%zu)\n"),
3851+
SIZE_MAX);
3852+
return NULL;
37823853
}
37833854

37843855
unsigned char *

src/interfaces/libpq/fe-print.c

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

111+
/*
112+
* Quick sanity check on po->fieldSep, since we make heavy use of int
113+
* math throughout.
114+
*/
115+
if (fs_len < strlen(po->fieldSep))
116+
{
117+
fprintf(stderr, libpq_gettext("overlong field separator\n"));
118+
goto exit;
119+
}
120+
111121
nTups = PQntuples(res);
112122
fieldNames = (const char **) calloc(nFields, sizeof(char *));
113123
fieldNotNum = (unsigned char *) calloc(nFields, 1);
@@ -409,7 +419,7 @@ do_field(const PQprintOpt *po, const PGresult *res,
409419
{
410420
if (plen > fieldMax[j])
411421
fieldMax[j] = plen;
412-
if (!(fields[i * nFields + j] = (char *) malloc(plen + 1)))
422+
if (!(fields[i * nFields + j] = (char *) malloc((size_t) plen + 1)))
413423
{
414424
fprintf(stderr, libpq_gettext("out of memory\n"));
415425
return false;
@@ -459,6 +469,27 @@ do_field(const PQprintOpt *po, const PGresult *res,
459469
}
460470

461471

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

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

547598

0 commit comments

Comments
 (0)