8000 Teach libpq to detect integer overflow in the row count of a PGresult. · lelisa2016/postgres@a07058a · GitHub
[go: up one dir, main page]

Skip to content

Commit a07058a

Browse files
committed
Teach libpq to detect integer overflow in the row count of a PGresult.
Adding more than 1 billion rows to a PGresult would overflow its ntups and tupArrSize fields, leading to client crashes. It'd be desirable to use wider fields on 64-bit machines, but because all of libpq's external APIs use plain "int" for row counters, that's going to be hard to accomplish without an ABI break. Given the lack of complaints so far, and the general pain that would be involved in using such huge PGresults, let's settle for just preventing the overflow and reporting a useful error message if it does happen. Also, for a couple more lines of code we can increase the threshold of trouble from INT_MAX/2 to INT_MAX rows. To do that, refactor pqAddTuple() to allow returning an error message that replaces the default assumption that it failed because of out-of-memory. Along the way, fix PQsetvalue() so that it reports all failures via pqInternalNotice(). It already did so in the case of bad field number, but neglected to report anything for other error causes. Because of the potential for crashes, this seems like a back-patchable bug fix, despite the lack of field reports. Michael Paquier, per a complaint from Igor Korot. Discussion: https://postgr.es/m/CA+FnnTxyLWyjY1goewmJNxC==HQCCF4fKkoCTa9qR36oRAHDPw@mail.gmail.com
1 parent d4ab780 commit a07058a

File tree

1 file changed

+60
-9
lines changed

1 file changed

+60
-9
lines changed

src/interfaces/libpq/fe-exec.c

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include <ctype.h>
1818
#include <fcntl.h>
19+
#include <limits.h>
1920

2021
#include "libpq-fe.h"
2122
#include "libpq-int.h"
@@ -51,7 +52,8 @@ static bool static_std_strings = false;
5152

5253

5354
static PGEvent *dupEvents(PGEvent *events, int count);
54-
static bool pqAddTuple(PGresult *res, PGresAttValue *tup);
55+
static bool pqAddTuple(PGresult *res, PGresAttValue *tup,
56+
const char **errmsgp);
5557
static bool PQsendQueryStart(PGconn *conn);
5658
static int PQsendQueryGuts(PGconn *conn,
5759
const char *command,
@@ -415,18 +417,26 @@ dupEvents(PGEvent *events, int count)
415417
* equal to PQntuples(res). If it is equal, a new tuple is created and
416418
* added to the result.
417419
* Returns a non-zero value for success and zero for failure.
420+
* (On failure, we report the specific problem via pqInternalNotice.)
418421
*/
419422
int
420423
PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len)
421424
{
422425
PGresAttValue *attval;
426+
const char *errmsg = NULL;
423427

428+
/* Note that this check also protects us against null "res" */
424429
if (!check_field_number(res, field_num))
425430
return FALSE;
426431

427432
/* Invalid tup_num, must be <= ntups */
428433
if (tup_num < 0 || tup_num > res->ntups)
434+
{
435+
pqInternalNotice(&res->noticeHooks,
436+
"row number %d is out of range 0..%d",
437+
tup_num, res->ntups);
429438
return FALSE;
439+
}
430440

431441
/* need to allocate a new tuple? */
432442
if (tup_num == res->ntups)
@@ -439,7 +449,7 @@ PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len)
439449
TRUE);
440450

441451
if (!tup)
442-
return FALSE;
452+
goto fail;
443453

444454
/* initialize each column to NULL */
445455
for (i = 0; i < res->numAttributes; i++)
@@ -449,8 +459,8 @@ PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len)
449459
}
450460

451461
/* add it to the array */
452-
if (!pqAddTuple(res, tup))
453-
return FALSE;
462+
if (!pqAddTuple(res, tup, &errmsg))
463+
goto fail;
454464
}
455465

456466
attval = &res->tuples[tup_num][field_num];
@@ -470,13 +480,24 @@ PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len)
470480
{
471481
attval->value = (char *) pqResultAlloc(res, len + 1, TRUE);
472482
if (!attval->value)
473-
return FALSE;
483+
goto fail;
474484
attval->len = len;
475485
memcpy(attval->value, value, len);
476486
attval->value[len] = '\0';
477487
}
478488

479489
return TRUE;
490+
491+
/*
492+
* Report failure via pqInternalNotice. If preceding code didn't provide
493+
* an error message, assume "out of memory" was meant.
494+
*/
495+
fail:
496+
if (!errmsg)
497+
errmsg = libpq_gettext("out of memory");
498+
pqInternalNotice(&res->noticeHooks, "%s", errmsg);
499+
500+
return FALSE;
480501
}
481502

482503
/*
@@ -845,10 +866,13 @@ pqInternalNotice(const PGNoticeHooks *hooks, const char *fmt,...)
845866
/*
846867
* pqAddTuple
847868
* add a row pointer to the PGresult structure, growing it if necessary
848-
* Returns TRUE if OK, FALSE if not enough memory to add the row
869+
* Returns TRUE if OK, FALSE if an error prevented adding the row
870+
*
871+
* On error, *errmsgp can be set to an error string to be returned.
872+
* If it is left NULL, the error is presumed to be "out of memory".
849873
*/
850874
static bool
851-
pqAddTuple(PGresult *res, PGresAttValue *tup)
875+
pqAddTuple(PGresult *res, PGresAttValue *tup, const char **errmsgp)
852876
{
853877
if (res->ntups >= res->tupArrSize)
854878
{
@@ -863,9 +887,36 @@ pqAddTuple(PGresult *res, PGresAttValue *tup)
863887
* existing allocation. Note that the positions beyond res->ntups are
864888
* garbage, not necessarily NULL.
865< 6DB6 /td>889
*/
866-
int newSize = (res->tupArrSize > 0) ? res->tupArrSize * 2 : 128;
890+
int newSize;
867891
PGresAttValue **newTuples;
868892

893+
/*
894+
* Since we use integers for row numbers, we can't support more than
895+
* INT_MAX rows. Make sure we allow that many, though.
896+
*/
897+
if (res->tupArrSize <= INT_MAX / 2)
898+
newSize = (res->tupArrSize > 0) ? res->tupArrSize * 2 : 128;
899+
else if (res->tupArrSize < INT_MAX)
900+
newSize = INT_MAX;
901+
else
902+
{
903+
*errmsgp = libpq_gettext("PGresult cannot support more than INT_MAX tuples");
904+
return FALSE;
905+
}
906+
907+
/*
908+
* Also, on 32-bit platforms we could, in theory, overflow size_t even
909+
* before newSize gets to INT_MAX. (In practice we'd doubtless hit
910+
* OOM long before that, but let's check.)
911+
*/
912+
#if INT_MAX >= (SIZE_MAX / 2)
913+
if (newSize > SIZE_MAX / sizeof(PGresAttValue *))
914+
{
915+
*errmsgp = libpq_gettext("size_t overflow");
916+
return FALSE;
917+
}
918+
#endif
919+
869920
if (res->tuples == NULL)
870921
newTuples = (PGresAttValue **)
871922
malloc(newSize * sizeof(PGresAttValue *));
@@ -1090,7 +1141,7 @@ pqRowProcessor(PGconn *conn, const char **errmsgp)
10901141
}
10911142

10921143
/* And add the tuple to the PGresult's tuple array */
1093-
if (!pqAddTuple(res, tup))
1144+
if (!pqAddTuple(res, tup, errmsgp))
10941145
goto fail;
10951146

10961147
/*

0 commit comments

Comments
 (0)
0