Commit 2e70d6b5 authored by Tom Lane's avatar Tom Lane

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
parent bf11e7ee
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include <ctype.h> #include <ctype.h>
#include <fcntl.h> #include <fcntl.h>
#include <limits.h>
#include "libpq-fe.h" #include "libpq-fe.h"
#include "libpq-int.h" #include "libpq-int.h"
...@@ -51,7 +52,8 @@ static bool static_std_strings = false; ...@@ -51,7 +52,8 @@ static bool static_std_strings = false;
static PGEvent *dupEvents(PGEvent *events, int count); static PGEvent *dupEvents(PGEvent *events, int count);
static bool pqAddTuple(PGresult *res, PGresAttValue *tup); static bool pqAddTuple(PGresult *res, PGresAttValue *tup,
const char **errmsgp);
static bool PQsendQueryStart(PGconn *conn); static bool PQsendQueryStart(PGconn *conn);
static int PQsendQueryGuts(PGconn *conn, static int PQsendQueryGuts(PGconn *conn,
const char *command, const char *command,
...@@ -416,18 +418,26 @@ dupEvents(PGEvent *events, int count) ...@@ -416,18 +418,26 @@ dupEvents(PGEvent *events, int count)
* equal to PQntuples(res). If it is equal, a new tuple is created and * equal to PQntuples(res). If it is equal, a new tuple is created and
* added to the result. * added to the result.
* Returns a non-zero value for success and zero for failure. * Returns a non-zero value for success and zero for failure.
* (On failure, we report the specific problem via pqInternalNotice.)
*/ */
int int
PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len) PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len)
{ {
PGresAttValue *attval; PGresAttValue *attval;
const char *errmsg = NULL;
/* Note that this check also protects us against null "res" */
if (!check_field_number(res, field_num)) if (!check_field_number(res, field_num))
return FALSE; return FALSE;
/* Invalid tup_num, must be <= ntups */ /* Invalid tup_num, must be <= ntups */
if (tup_num < 0 || tup_num > res->ntups) if (tup_num < 0 || tup_num > res->ntups)
{
pqInternalNotice(&res->noticeHooks,
"row number %d is out of range 0..%d",
tup_num, res->ntups);
return FALSE; return FALSE;
}
/* need to allocate a new tuple? */ /* need to allocate a new tuple? */
if (tup_num == res->ntups) if (tup_num == res->ntups)
...@@ -440,7 +450,7 @@ PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len) ...@@ -440,7 +450,7 @@ PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len)
TRUE); TRUE);
if (!tup) if (!tup)
return FALSE; goto fail;
/* initialize each column to NULL */ /* initialize each column to NULL */
for (i = 0; i < res->numAttributes; i++) for (i = 0; i < res->numAttributes; i++)
...@@ -450,8 +460,8 @@ PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len) ...@@ -450,8 +460,8 @@ PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len)
} }
/* add it to the array */ /* add it to the array */
if (!pqAddTuple(res, tup)) if (!pqAddTuple(res, tup, &errmsg))
return FALSE; goto fail;
} }
attval = &res->tuples[tup_num][field_num]; attval = &res->tuples[tup_num][field_num];
...@@ -471,13 +481,24 @@ PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len) ...@@ -471,13 +481,24 @@ PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len)
{ {
attval->value = (char *) pqResultAlloc(res, len + 1, TRUE); attval->value = (char *) pqResultAlloc(res, len + 1, TRUE);
if (!attval->value) if (!attval->value)
return FALSE; goto fail;
attval->len = len; attval->len = len;
memcpy(attval->value, value, len); memcpy(attval->value, value, len);
attval->value[len] = '\0'; attval->value[len] = '\0';
} }
return TRUE; return TRUE;
/*
* Report failure via pqInternalNotice. If preceding code didn't provide
* an error message, assume "out of memory" was meant.
*/
fail:
if (!errmsg)
errmsg = libpq_gettext("out of memory");
pqInternalNotice(&res->noticeHooks, "%s", errmsg);
return FALSE;
} }
/* /*
...@@ -847,10 +868,13 @@ pqInternalNotice(const PGNoticeHooks *hooks, const char *fmt,...) ...@@ -847,10 +868,13 @@ pqInternalNotice(const PGNoticeHooks *hooks, const char *fmt,...)
/* /*
* pqAddTuple * pqAddTuple
* add a row pointer to the PGresult structure, growing it if necessary * add a row pointer to the PGresult structure, growing it if necessary
* Returns TRUE if OK, FALSE if not enough memory to add the row * Returns TRUE if OK, FALSE if an error prevented adding the row
*
* On error, *errmsgp can be set to an error string to be returned.
* If it is left NULL, the error is presumed to be "out of memory".
*/ */
static bool static bool
pqAddTuple(PGresult *res, PGresAttValue *tup) pqAddTuple(PGresult *res, PGresAttValue *tup, const char **errmsgp)
{ {
if (res->ntups >= res->tupArrSize) if (res->ntups >= res->tupArrSize)
{ {
...@@ -865,9 +889,36 @@ pqAddTuple(PGresult *res, PGresAttValue *tup) ...@@ -865,9 +889,36 @@ pqAddTuple(PGresult *res, PGresAttValue *tup)
* existing allocation. Note that the positions beyond res->ntups are * existing allocation. Note that the positions beyond res->ntups are
* garbage, not necessarily NULL. * garbage, not necessarily NULL.
*/ */
int newSize = (res->tupArrSize > 0) ? res->tupArrSize * 2 : 128; int newSize;
PGresAttValue **newTuples; PGresAttValue **newTuples;
/*
* Since we use integers for row numbers, we can't support more than
* INT_MAX rows. Make sure we allow that many, though.
*/
if (res->tupArrSize <= INT_MAX / 2)
newSize = (res->tupArrSize > 0) ? res->tupArrSize * 2 : 128;
else if (res->tupArrSize < INT_MAX)
newSize = INT_MAX;
else
{
*errmsgp = libpq_gettext("PGresult cannot support more than INT_MAX tuples");
return FALSE;
}
/*
* Also, on 32-bit platforms we could, in theory, overflow size_t even
* before newSize gets to INT_MAX. (In practice we'd doubtless hit
* OOM long before that, but let's check.)
*/
#if INT_MAX >= (SIZE_MAX / 2)
if (newSize > SIZE_MAX / sizeof(PGresAttValue *))
{
*errmsgp = libpq_gettext("size_t overflow");
return FALSE;
}
#endif
if (res->tuples == NULL) if (res->tuples == NULL)
newTuples = (PGresAttValue **) newTuples = (PGresAttValue **)
malloc(newSize * sizeof(PGresAttValue *)); malloc(newSize * sizeof(PGresAttValue *));
...@@ -1093,7 +1144,7 @@ pqRowProcessor(PGconn *conn, const char **errmsgp) ...@@ -1093,7 +1144,7 @@ pqRowProcessor(PGconn *conn, const char **errmsgp)
} }
/* And add the tuple to the PGresult's tuple array */ /* And add the tuple to the PGresult's tuple array */
if (!pqAddTuple(res, tup)) if (!pqAddTuple(res, tup, errmsgp))
goto fail; goto fail;
/* /*
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment