From d9125a0e48febc560c15a57dea84964b90d53ef3 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Tue, 26 May 2015 15:58:37 +0000 Subject: [PATCH] Improve OOM detection of parseInput in libpq An OOM occurring at the beginning of parseInput() when called at the beginning of pqGetResult could result in libpq hanging indefinitely. The probability to face this case is rather low, but let's make the OOM error handling of parseInput more robust in this area. Additionally, a couple of underlying functions like getCopyStart() are made a bit smarter to make the difference between an OOM, meaning that input parsing should be immediately stopped, and cases where there is not enough data, meaning that parsing should continue later on, making the whole more robust. --- src/interfaces/libpq/fe-connect.c | 9 +- src/interfaces/libpq/fe-exec.c | 27 +-- src/interfaces/libpq/fe-protocol2.c | 204 +++++++++++++++-------- src/interfaces/libpq/fe-protocol3.c | 320 +++++++++++++++++++++++++----------- src/interfaces/libpq/libpq-int.h | 4 +- 5 files changed, 382 insertions(+), 182 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index a45f4cb..9772f7c 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -2366,11 +2366,18 @@ keep_going: /* We will come back to here until there is { if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3) { - if (pqGetErrorNotice3(conn, true)) + int res = pqGetErrorNotice3(conn, true); + if (res == -1) { /* We'll come back when there is more data */ return PGRES_POLLING_READING; } + else if (res == -2) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory")); + goto error_return; + } } else { diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 828f18e..754d7f3 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -62,7 +62,7 @@ static int PQsendQueryGuts(PGconn *conn, const int *paramLengths, const int *paramFormats, int resultFormat); -static void parseInput(PGconn *conn); +static int parseInput(PGconn *conn); static PGresult *getCopyResult(PGconn *conn, ExecStatusType copytype); static bool PQexecStart(PGconn *conn); static PGresult *PQexecFinish(PGconn *conn); @@ -1573,7 +1573,7 @@ pqHandleSendFailure(PGconn *conn) * Parse any available input messages. Since we are in PGASYNC_IDLE * state, only NOTICE and NOTIFY messages will be eaten. */ - parseInput(conn); + (void) parseInput(conn); } /* @@ -1641,14 +1641,16 @@ PQconsumeInput(PGconn *conn) * parseInput: if appropriate, parse input data from backend * until input is exhausted or a stopping state is reached. * Note that this function will NOT attempt to read more data from the backend. + * Exit: returns 0 if message is successfully parsed. + * returns EOF if an error occurs. */ -static void +static int parseInput(PGconn *conn) { if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3) - pqParseInput3(conn); + return pqParseInput3(conn); else - pqParseInput2(conn); + return pqParseInput2(conn); } /* @@ -1663,7 +1665,8 @@ PQisBusy(PGconn *conn) return FALSE; /* Parse any available data, if our state permits. */ - parseInput(conn); + if (parseInput(conn)) + return FALSE; /* PQgetResult will return immediately in all states except BUSY. */ return conn->asyncStatus == PGASYNC_BUSY; @@ -1686,7 +1689,8 @@ PQgetResult(PGconn *conn) return NULL; /* Parse any available data, if our state permits. */ - parseInput(conn); + if (parseInput(conn)) + return NULL; /* If not ready to return something, block until we are. */ while (conn->asyncStatus == PGASYNC_BUSY) @@ -1721,7 +1725,8 @@ PQgetResult(PGconn *conn) } /* Parse it. */ - parseInput(conn); + if (parseInput(conn)) + return NULL; } /* Return the appropriate thing. */ @@ -2177,7 +2182,8 @@ PQnotifies(PGconn *conn) return NULL; /* Parse any available data to see if we can extract NOTIFY messages. */ - parseInput(conn); + if (parseInput(conn)) + return NULL; event = conn->notifyHead; if (event) @@ -2217,7 +2223,8 @@ PQputCopyData(PGconn *conn, const char *buffer, int nbytes) * input data into the input buffer happens down inside pqSendSome, but * it's not authorized to get rid of the data again.) */ - parseInput(conn); + if (parseInput(conn)) + return -1; if (nbytes > 0) { diff --git a/src/interfaces/libpq/fe-protocol2.c b/src/interfaces/libpq/fe-protocol2.c index eeba7f3..5ac3d9a 100644 --- a/src/interfaces/libpq/fe-protocol2.c +++ b/src/interfaces/libpq/fe-protocol2.c @@ -406,8 +406,10 @@ error_return: * parseInput: if appropriate, parse input data from backend * until input is exhausted or a stopping state is reached. * Note that this function will NOT attempt to read more data from the backend. + * Exit: returns 0 if successfully parsed input. + * returns EOF if an error occurs. */ -void +int pqParseInput2(PGconn *conn) { char id; @@ -424,14 +426,14 @@ pqParseInput2(PGconn *conn) * protocol and have identifying leading characters.) */ if (conn->asyncStatus == PGASYNC_COPY_OUT) - return; + return 0; /* * OK to try to read a message type code. */ conn->inCursor = conn->inStart; if (pqGetc(&id, conn)) - return; + return 0; /* * NOTIFY and NOTICE messages can happen in any state besides COPY @@ -447,18 +449,21 @@ pqParseInput2(PGconn *conn) if (id == 'A') { if (getNotify(conn)) - return; + return 0; } else if (id == 'N') { - if (pqGetErrorNotice2(conn, false)) - return; + int res = pqGetErrorNotice2(conn, false); + if (res == -1) + return 0; + else if (res == -2) + goto failure; } else if (conn->asyncStatus != PGASYNC_BUSY) { /* If not IDLE state, just wait ... */ if (conn->asyncStatus != PGASYNC_IDLE) - return; + return 0; /* * Unexpected message in IDLE state; need to recover somehow. @@ -470,8 +475,12 @@ pqParseInput2(PGconn *conn) */ if (id == 'E') { - if (pqGetErrorNotice2(conn, false /* treat as notice */ )) - return; + /* treat as notice */ + int res = pqGetErrorNotice2(conn, false); + if (res == -1) + return 0; + else if (res == -2) + goto failure; } else { @@ -492,13 +501,13 @@ pqParseInput2(PGconn *conn) { case 'C': /* command complete */ if (pqGets(&conn->workBuffer, conn)) - return; + return 0; if (conn->result == NULL) { conn->result = PQmakeEmptyPGresult(conn, PGRES_COMMAND_OK); if (!conn->result) - return; + goto failure; } strlcpy(conn->result->cmdStatus, conn->workBuffer.data, CMDSTATUS_LEN); @@ -506,9 +515,14 @@ pqParseInput2(PGconn *conn) conn->asyncStatus = PGASYNC_READY; break; case 'E': /* error return */ - if (pqGetErrorNotice2(conn, true)) - return; - conn->asyncStatus = PGASYNC_READY; + { + int res = pqGetErrorNotice2(conn, true); + if (res == -1) + return 0; + else if (res == -2) + goto failure; + conn->asyncStatus = PGASYNC_READY; + } break; case 'Z': /* backend is ready for new query */ conn->asyncStatus = PGASYNC_IDLE; @@ -516,7 +530,7 @@ pqParseInput2(PGconn *conn) case 'I': /* empty query */ /* read and throw away the closing '\0' */ if (pqGetc(&id, conn)) - return; + return 0; if (id != '\0') pqInternalNotice(&conn->noticeHooks, "unexpected character %c following empty query response (\"I\" message)", @@ -524,6 +538,8 @@ pqParseInput2(PGconn *conn) if (conn->result == NULL) conn->result = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY); + if (conn->result == NULL) + goto failure; conn->asyncStatus = PGASYNC_READY; break; case 'K': /* secret key data from the backend */ @@ -534,21 +550,24 @@ pqParseInput2(PGconn *conn) * Save the data and continue processing. */ if (pqGetInt(&(conn->be_pid), 4, conn)) - return; + return 0; if (pqGetInt(&(conn->be_key), 4, conn)) - return; + return 0; break; case 'P': /* synchronous (normal) portal */ if (pqGets(&conn->workBuffer, conn)) - return; + return 0; /* We pretty much ignore this message type... */ break; case 'T': /* row descriptions (start of query results) */ if (conn->result == NULL) { + int res = getRowDescriptions(conn); /* First 'T' in a query sequence */ - if (getRowDescriptions(conn)) - return; + if (res == -1) + return 0; + if (res == -2) + goto failure; /* getRowDescriptions() moves inStart itself */ continue; } @@ -562,15 +581,17 @@ pqParseInput2(PGconn *conn) * result. */ conn->asyncStatus = PGASYNC_READY; - return; + return 0; } break; case 'D': /* ASCII data tuple */ if (conn->result != NULL) { - /* Read another tuple of a normal query response */ - if (getAnotherTuple(conn, FALSE)) - return; + int res = getAnotherTuple(conn, FALSE); + if (res == -1) + return 0; + else if (res == -2) + goto failure; /* getAnotherTuple() moves inStart itself */ continue; } @@ -580,15 +601,17 @@ pqParseInput2(PGconn *conn) "server sent data (\"D\" message) without prior row description (\"T\" message)"); /* Discard the unexpected message; good idea?? */ conn->inStart = conn->inEnd; - return; + return 0; } break; case 'B': /* Binary data tuple */ if (conn->result != NULL) { - /* Read another tuple of a normal query response */ - if (getAnotherTuple(conn, TRUE)) - return; + int res = getAnotherTuple(conn, TRUE); + if (res == -1) + return 0; + else if (res == -2) + goto failure; /* getAnotherTuple() moves inStart itself */ continue; } @@ -598,7 +621,7 @@ pqParseInput2(PGconn *conn) "server sent binary data (\"B\" message) without prior row description (\"T\" message)"); /* Discard the unexpected message; good idea?? */ conn->inStart = conn->inEnd; - return; + return 0; } break; case 'G': /* Start Copy In */ @@ -622,19 +645,28 @@ pqParseInput2(PGconn *conn) /* Discard the unexpected message; good idea?? */ conn->inStart = conn->inEnd; conn->asyncStatus = PGASYNC_READY; - return; + return 0; } /* switch on protocol character */ } /* Successfully consumed this message */ conn->inStart = conn->inCursor; } + + /* we are done */ + return 0; + +failure: + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return EOF; } /* * parseInput subroutine to read a 'T' (row descriptions) message. * We build a PGresult structure containing the attribute data. - * Returns: 0 if completed message, EOF if error or not enough data - * received yet. + * Returns: 0 if completed message. + * -1 if error or not enough data received yet. + * -2 in case of OOM. * * Note that if we run out of data, we have to suspend and reprocess * the message after more data is received. Otherwise, conn->inStart @@ -643,22 +675,26 @@ pqParseInput2(PGconn *conn) static int getRowDescriptions(PGconn *conn) { - PGresult *result; + PGresult *result = NULL; int nfields; - const char *errmsg; + const char *errmsg = NULL; int i; + int status = 0; result = PQmakeEmptyPGresult(conn, PGRES_TUPLES_OK); if (!result) { - errmsg = NULL; /* means "out of memory", see below */ + status = -2; goto advance_and_error; } /* parseInput already read the 'T' label. */ /* the next two bytes are the number of fields */ if (pqGetInt(&(result->numAttributes), 2, conn)) + { + status = -1; goto EOFexit; + } nfields = result->numAttributes; /* allocate space for the attribute descriptors */ @@ -668,7 +704,7 @@ getRowDescriptions(PGconn *conn) pqResultAlloc(result, nfields * sizeof(PGresAttDesc), TRUE); if (!result->attDescs) { - errmsg = NULL; /* means "out of memory", see below */ + status = -2; goto advance_and_error; } MemSet(result->attDescs, 0, nfields * sizeof(PGresAttDesc)); @@ -685,7 +721,10 @@ getRowDescriptions(PGconn *conn) pqGetInt(&typid, 4, conn) || pqGetInt(&typlen, 2, conn) || pqGetInt(&atttypmod, 4, conn)) + { + status = -1; goto EOFexit; + } /* * Since pqGetInt treats 2-byte integers as unsigned, we need to @@ -697,7 +736,7 @@ getRowDescriptions(PGconn *conn) conn->workBuffer.data); if (!result->attDescs[i].name) { - errmsg = NULL; /* means "out of memory", see below */ + status = -2; goto advance_and_error; } result->attDescs[i].tableid = 0; @@ -720,7 +759,7 @@ getRowDescriptions(PGconn *conn) */ /* And we're done. */ - return 0; + return status; advance_and_error: @@ -738,15 +777,10 @@ advance_and_error: pqClearAsyncResult(conn); /* - * If preceding code didn't provide an error message, assume "out of - * memory" was meant. The advantage of having this special case is that - * freeing the old result first greatly improves the odds that gettext() - * will succeed in providing a translation. + * If preceding code has set an error message, save it. */ - if (!errmsg) - errmsg = libpq_gettext("out of memory for query result"); - - printfPQExpBuffer(&conn->errorMessage, "%s\n", errmsg); + if (errmsg != NULL) + printfPQExpBuffer(&conn->errorMessage, "%s\n", errmsg); /* * XXX: if PQmakeEmptyPGresult() fails, there's probably not much we can @@ -758,14 +792,15 @@ advance_and_error: EOFexit: if (result && result != conn->result) PQclear(result); - return EOF; + return status; } /* * parseInput subroutine to read a 'B' or 'D' (row data) message. * We fill rowbuf with column pointers and then call the row processor. - * Returns: 0 if completed message, EOF if error or not enough data - * received yet. + * Returns: 0 if processed message successfully. + * -1 if not enough data. + * -2 if an OOM error happened. * * Note that if we run out of data, we have to suspend and reprocess * the message after more data is received. Otherwise, conn->inStart @@ -776,8 +811,9 @@ getAnotherTuple(PGconn *conn, bool binary) { PGresult *result = conn->result; int nfields = result->numAttributes; - const char *errmsg; + const char *errmsg = NULL; PGdataValue *rowbuf; + int status = 0; /* the backend sends us a bitmap of which attributes are null */ char std_bitmap[64]; /* used unless it doesn't fit */ @@ -797,7 +833,7 @@ getAnotherTuple(PGconn *conn, bool binary) nfields * sizeof(PGdataValue)); if (!rowbuf) { - errmsg = NULL; /* means "out of memory", see below */ + status = -2; goto advance_and_error; } conn->rowBuf = rowbuf; @@ -825,13 +861,16 @@ getAnotherTuple(PGconn *conn, bool binary) bitmap = (char *) malloc(nbytes); if (!bitmap) { - errmsg = NULL; /* means "out of memory", see below */ + status = -2; goto advance_and_error; } } if (pqGetnchar(bitmap, nbytes, conn)) + { + status = -1; goto EOFexit; + } /* Scan the fields */ bitmap_index = 0; @@ -844,7 +883,10 @@ getAnotherTuple(PGconn *conn, bool binary) if (!(bmap & 0200)) vlen = NULL_LEN; else if (pqGetInt(&vlen, 4, conn)) + { + status = -1; goto EOFexit; + } else { if (!binary) @@ -865,7 +907,10 @@ getAnotherTuple(PGconn *conn, bool binary) if (vlen > 0) { if (pqSkipnchar(vlen, conn)) + { + status = -1; goto EOFexit; + } } /* advance the bitmap stuff */ @@ -913,15 +958,13 @@ set_error_result: pqClearAsyncResult(conn); /* - * If preceding code didn't provide an error message, assume "out of - * memory" was meant. The advantage of having this special case is that - * freeing the old result first greatly improves the odds that gettext() - * will succeed in providing a translation. + * If preceding code has set an error message, save it. */ - if (!errmsg) - errmsg = libpq_gettext("out of memory for query result"); - - printfPQExpBuffer(&conn->errorMessage, "%s\n", errmsg); + if (errmsg != NULL) + { + printfPQExpBuffer(&conn->errorMessage, "%s\n", errmsg); + pqSaveErrorResult(conn); + } /* * XXX: if PQmakeEmptyPGresult() fails, there's probably not much we can @@ -933,7 +976,7 @@ set_error_result: EOFexit: if (bitmap != NULL && bitmap != std_bitmap) free(bitmap); - return EOF; + return status; } @@ -942,7 +985,8 @@ EOFexit: * This is possible in several places, so we break it out as a subroutine. * Entry: 'E' or 'N' message type has already been consumed. * Exit: returns 0 if successfully consumed message. - * returns EOF if not enough data. + * returns -1 if not enough data. + * returns -2 in case of out-of-memory. */ static int pqGetErrorNotice2(PGconn *conn, bool isError) @@ -951,6 +995,7 @@ pqGetErrorNotice2(PGconn *conn, bool isError) PQExpBufferData workBuf; char *startp; char *splitp; + int status = 0; /* * Since the message might be pretty long, we create a temporary @@ -959,7 +1004,10 @@ pqGetErrorNotice2(PGconn *conn, bool isError) */ initPQExpBuffer(&workBuf); if (pqGets(&workBuf, conn)) + { + status = -1; goto failure; + } /* * Make a PGresult to hold the message. We temporarily lie about the @@ -968,11 +1016,17 @@ pqGetErrorNotice2(PGconn *conn, bool isError) */ res = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY); if (!res) + { + status = -2; goto failure; + } res->resultStatus = isError ? PGRES_FATAL_ERROR : PGRES_NONFATAL_ERROR; res->errMsg = pqResultStrdup(res, workBuf.data); if (!res->errMsg) + { + status = -2; goto failure; + } /* * Break the message into fields. We can't do very much here, but we can @@ -1036,13 +1090,13 @@ pqGetErrorNotice2(PGconn *conn, bool isError) } termPQExpBuffer(&workBuf); - return 0; + return status; failure: if (res) PQclear(res); termPQExpBuffer(&workBuf); - return EOF; + return status; } /* @@ -1521,9 +1575,14 @@ pqFunctionCall2(PGconn *conn, Oid fnid, } break; case 'E': /* error return */ - if (pqGetErrorNotice2(conn, true)) - continue; - status = PGRES_FATAL_ERROR; + { + int res = pqGetErrorNotice3(conn, true); + if (res == -1) + continue; + else if (res == -2) + return NULL; + status = PGRES_FATAL_ERROR; + } break; case 'A': /* notify message */ /* handle notify and go back to processing return values */ @@ -1531,9 +1590,14 @@ pqFunctionCall2(PGconn *conn, Oid fnid, continue; break; case 'N': /* notice */ - /* handle notice and go back to processing return values */ - if (pqGetErrorNotice2(conn, false)) - continue; + { + int res = pqGetErrorNotice3(conn, false); + if (res == -1) + continue; + else if (res == -2) + return NULL; + status = PGRES_FATAL_ERROR; + } break; case 'Z': /* backend is ready for new query */ /* consume the message and exit */ diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index a847f08..58382e0 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -61,8 +61,10 @@ static int build_startup_packet(const PGconn *conn, char *packet, * parseInput: if appropriate, parse input data from backend * until input is exhausted or a stopping state is reached. * Note that this function will NOT attempt to read more data from the backend. + * Exit: returns 0 if message is successfully parsed. + * returns EOF if an error occurs. */ -void +int pqParseInput3(PGconn *conn) { char id; @@ -80,9 +82,9 @@ pqParseInput3(PGconn *conn) */ conn->inCursor = conn->inStart; if (pqGetc(&id, conn)) - return; + return 0; if (pqGetInt(&msgLength, 4, conn)) - return; + return 0; /* * Try to validate message type/length here. A length less than 4 is @@ -92,12 +94,12 @@ pqParseInput3(PGconn *conn) if (msgLength < 4) { handleSyncLoss(conn, id, msgLength); - return; + return 0; } if (msgLength > 30000 && !VALID_LONG_MESSAGE_TYPE(id)) { handleSyncLoss(conn, id, msgLength); - return; + return 0; } /* @@ -126,7 +128,7 @@ pqParseInput3(PGconn *conn) */ handleSyncLoss(conn, id, msgLength); } - return; + return 0; } /* @@ -148,18 +150,21 @@ pqParseInput3(PGconn *conn) if (id == 'A') { if (getNotify(conn)) - return; + return 0; } else if (id == 'N') { - if (pqGetErrorNotice3(conn, false)) - return; + int res = pqGetErrorNotice3(conn, false); + if (res == -1) + return 0; + else if (res == -2) + goto failure; } else if (conn->asyncStatus != PGASYNC_BUSY) { /* If not IDLE state, just wait ... */ if (conn->asyncStatus != PGASYNC_IDLE) - return; + return 0; /* * Unexpected message in IDLE state; need to recover somehow. @@ -172,13 +177,17 @@ pqParseInput3(PGconn *conn) */ if (id == 'E') { - if (pqGetErrorNotice3(conn, false /* treat as notice */ )) - return; + /* treat as notice */ + int res = pqGetErrorNotice3(conn, false); + if (res == -1) + return 0; + else if (res == -2) + goto failure; } else if (id == 'S') { if (getParameterStatus(conn)) - return; + return 0; } else { @@ -198,26 +207,31 @@ pqParseInput3(PGconn *conn) { case 'C': /* command complete */ if (pqGets(&conn->workBuffer, conn)) - return; + return 0; if (conn->result == NULL) { conn->result = PQmakeEmptyPGresult(conn, PGRES_COMMAND_OK); if (!conn->result) - return; + goto failure; } strlcpy(conn->result->cmdStatus, conn->workBuffer.data, CMDSTATUS_LEN); conn->asyncStatus = PGASYNC_READY; break; case 'E': /* error return */ - if (pqGetErrorNotice3(conn, true)) - return; - conn->asyncStatus = PGASYNC_READY; + { + int res = pqGetErrorNotice3(conn, true); + if (res == -1) + return 0; + else if (res == -2) + goto failure; + conn->asyncStatus = PGASYNC_READY; + } break; case 'Z': /* backend is ready for new query */ if (getReadyForQuery(conn)) - return; + return 0; conn->asyncStatus = PGASYNC_IDLE; break; case 'I': /* empty query */ @@ -226,7 +240,7 @@ pqParseInput3(PGconn *conn) conn->result = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY); if (!conn->result) - return; + goto failure; } conn->asyncStatus = PGASYNC_READY; break; @@ -239,7 +253,7 @@ pqParseInput3(PGconn *conn) conn->result = PQmakeEmptyPGresult(conn, PGRES_COMMAND_OK); if (!conn->result) - return; + goto failure; } conn->asyncStatus = PGASYNC_READY; } @@ -250,7 +264,7 @@ pqParseInput3(PGconn *conn) break; case 'S': /* parameter status */ if (getParameterStatus(conn)) - return; + return 0; break; case 'K': /* secret key data from the backend */ @@ -260,17 +274,20 @@ pqParseInput3(PGconn *conn) * Save the data and continue processing. */ if (pqGetInt(&(conn->be_pid), 4, conn)) - return; + return 0; if (pqGetInt(&(conn->be_key), 4, conn)) - return; + return 0; break; case 'T': /* Row Description */ if (conn->result == NULL || conn->queryclass == PGQUERY_DESCRIBE) { /* First 'T' in a query sequence */ - if (getRowDescriptions(conn, msgLength)) - return; + int res = getRowDescriptions(conn, msgLength); + if (res == -1) + return 0; + if (res == -2) + goto failure; /* getRowDescriptions() moves inStart itself */ continue; } @@ -284,7 +301,7 @@ pqParseInput3(PGconn *conn) * result. */ conn->asyncStatus = PGASYNC_READY; - return; + return 0; } break; case 'n': /* No Data */ @@ -306,22 +323,29 @@ pqParseInput3(PGconn *conn) conn->result = PQmakeEmptyPGresult(conn, PGRES_COMMAND_OK); if (!conn->result) - return; + goto failure; } conn->asyncStatus = PGASYNC_READY; } break; case 't': /* Parameter Description */ - if (getParamDescriptions(conn)) - return; + { + int res = getParamDescriptions(conn); + if (res == -1) + return 0; + else if (res == -2) + goto failure; + } break; case 'D': /* Data Row */ if (conn->result != NULL && conn->result->resultStatus == PGRES_TUPLES_OK) { - /* Read another tuple of a normal query response */ - if (getAnotherTuple(conn, msgLength)) - return; + int res = getAnotherTuple(conn, msgLength); + if (res == -1) + return 0; + else if (res == -2) + goto failure; /* getAnotherTuple() moves inStart itself */ continue; } @@ -345,21 +369,36 @@ pqParseInput3(PGconn *conn) } break; case 'G': /* Start Copy In */ - if (getCopyStart(conn, PGRES_COPY_IN)) - return; - conn->asyncStatus = PGASYNC_COPY_IN; + { + int res = getCopyStart(conn, PGRES_COPY_IN); + if (res == -1) + return 0; + else if (res == -2) + goto failure; + conn->asyncStatus = PGASYNC_COPY_IN; + } break; case 'H': /* Start Copy Out */ - if (getCopyStart(conn, PGRES_COPY_OUT)) - return; - conn->asyncStatus = PGASYNC_COPY_OUT; - conn->copy_already_done = 0; + { + int res = getCopyStart(conn, PGRES_COPY_OUT); + if (res == -1) + return 0; + else if (res == -2) + goto failure; + conn->asyncStatus = PGASYNC_COPY_OUT; + conn->copy_already_done = 0; + } break; case 'W': /* Start Copy Both */ - if (getCopyStart(conn, PGRES_COPY_BOTH)) - return; - conn->asyncStatus = PGASYNC_COPY_BOTH; - conn->copy_already_done = 0; + { + int res = getCopyStart(conn, PGRES_COPY_BOTH); + if (res == -1) + return 0; + else if (res == -2) + goto failure; + conn->asyncStatus = PGASYNC_COPY_BOTH; + conn->copy_already_done = 0; + } break; case 'd': /* Copy Data */ @@ -412,6 +451,14 @@ pqParseInput3(PGconn *conn) conn->inStart += 5 + msgLength; } } + + /* we are done */ + return 0; + +failure: + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return EOF; } /* @@ -438,8 +485,9 @@ handleSyncLoss(PGconn *conn, char id, int msgLength) * parseInput subroutine to read a 'T' (row descriptions) message. * We'll build a new PGresult structure (unless called for a Describe * command for a prepared statement) containing the attribute data. - * Returns: 0 if processed message successfully, EOF to suspend parsing - * (the latter case is not actually used currently). + * Returns: 0 if processed message successfully. + * -1 if not enough data. + * -2 in case of OOM error. * In either case, conn->inStart has been advanced past the message. */ static int @@ -447,8 +495,9 @@ getRowDescriptions(PGconn *conn, int msgLength) { PGresult *result; int nfields; - const char *errmsg; + const char *errmsg = NULL; int i; + int status = 0; /* * When doing Describe for a prepared statement, there'll already be a @@ -466,7 +515,7 @@ getRowDescriptions(PGconn *conn, int msgLength) result = PQmakeEmptyPGresult(conn, PGRES_TUPLES_OK); if (!result) { - errmsg = NULL; /* means "out of memory", see below */ + status = -2; goto advance_and_error; } @@ -476,6 +525,7 @@ getRowDescriptions(PGconn *conn, int msgLength) { /* We should not run out of data here, so complain */ errmsg = libpq_gettext("insufficient data in \"T\" message"); + status = -1; goto advance_and_error; } nfields = result->numAttributes; @@ -487,7 +537,7 @@ getRowDescriptions(PGconn *conn, int msgLength) pqResultAlloc(result, nfields * sizeof(PGresAttDesc), TRUE); if (!result->attDescs) { - errmsg = NULL; /* means "out of memory", see below */ + status = -2; goto advance_and_error; } MemSet(result->attDescs, 0, nfields * sizeof(PGresAttDesc)); @@ -516,6 +566,7 @@ getRowDescriptions(PGconn *conn, int msgLength) { /* We should not run out of data here, so complain */ errmsg = libpq_gettext("insufficient data in \"T\" message"); + status = -1; goto advance_and_error; } @@ -531,7 +582,7 @@ getRowDescriptions(PGconn *conn, int msgLength) conn->workBuffer.data); if (!result->attDescs[i].name) { - errmsg = NULL; /* means "out of memory", see below */ + status = -2; goto advance_and_error; } result->attDescs[i].tableid = tableid; @@ -549,6 +600,7 @@ getRowDescriptions(PGconn *conn, int msgLength) if (conn->inCursor != conn->inStart + 5 + msgLength) { errmsg = libpq_gettext("extraneous data in \"T\" message"); + status = -1; goto advance_and_error; } @@ -574,7 +626,7 @@ getRowDescriptions(PGconn *conn, int msgLength) */ /* And we're done. */ - return 0; + return status; advance_and_error: /* Discard unsaved result, if any */ @@ -591,29 +643,30 @@ advance_and_error: pqClearAsyncResult(conn); /* - * If preceding code didn't provide an error message, assume "out of - * memory" was meant. The advantage of having this special case is that - * freeing the old result first greatly improves the odds that gettext() - * will succeed in providing a translation. + * If preceding code has set an error message, save it. */ - if (!errmsg) - errmsg = libpq_gettext("out of memory for query result"); - - printfPQExpBuffer(&conn->errorMessage, "%s\n", errmsg); - pqSaveErrorResult(conn); + if (errmsg != NULL) + { + printfPQExpBuffer(&conn->errorMessage, "%s\n", errmsg); + pqSaveErrorResult(conn); + } /* - * Return zero to allow input parsing to continue. Subsequent "D" - * messages will be ignored until we get to end of data, since an error - * result is already set up. + * Return -1 to allow input parsing to continue in case of error. + * Subsequent "D" messages will be ignored until we get to end of + * data in this case, since an error result is already set up. If + * an out-of-memory happens, bail out immediately though and let + * the caller know that. */ - return 0; + return status; } /* * parseInput subroutine to read a 't' (ParameterDescription) message. * We'll build a new PGresult structure containing the parameter data. - * Returns: 0 if completed message, EOF if not enough data yet. + * Returns: 0 if completed message + * -1 if not enough data yet. + * -2 in case of an OOM error. * * Note that if we run out of data, we have to release the partially * constructed PGresult, and rebuild it again next time. Fortunately, @@ -625,15 +678,22 @@ getParamDescriptions(PGconn *conn) PGresult *result; int nparams; int i; + int res = 0; result = PQmakeEmptyPGresult(conn, PGRES_COMMAND_OK); if (!result) + { + res = -2; goto failure; + } /* parseInput already read the 't' label and message length. */ /* the next two bytes are the number of parameters */ if (pqGetInt(&(result->numParameters), 2, conn)) + { + res = -1; goto failure; + } nparams = result->numParameters; /* allocate space for the parameter descriptors */ @@ -642,7 +702,10 @@ getParamDescriptions(PGconn *conn) result->paramDescs = (PGresParamDesc *) pqResultAlloc(result, nparams * sizeof(PGresParamDesc), TRUE); if (!result->paramDescs) + { + res = -2; goto failure; + } MemSet(result->paramDescs, 0, nparams * sizeof(PGresParamDesc)); } @@ -652,24 +715,29 @@ getParamDescriptions(PGconn *conn) int typid; if (pqGetInt(&typid, 4, conn)) + { + res = -1; goto failure; + } result->paramDescs[i].typid = typid; } /* Success! */ conn->result = result; - return 0; + return res; failure: - PQclear(result); - return EOF; + if (result) + PQclear(result); + return res; } /* * parseInput subroutine to read a 'D' (row data) message. * We fill rowbuf with column pointers and then call the row processor. - * Returns: 0 if processed message successfully, EOF to suspend parsing - * (the latter case is not actually used currently). + * Returns: 0 if processed message successfully. + * -1 if not enough data. + * -2 if an OOM error happened. * In either case, conn->inStart has been advanced past the message. */ static int @@ -677,23 +745,26 @@ getAnotherTuple(PGconn *conn, int msgLength) { PGresult *result = conn->result; int nfields = result->numAttributes; - const char *errmsg; + const char *errmsg = NULL; PGdataValue *rowbuf; int tupnfields; /* # fields from tuple */ int vlen; /* length of the current field value */ int i; + int status = 0; /* Get the field count and make sure it's what we expect */ if (pqGetInt(&tupnfields, 2, conn)) { /* We should not run out of data here, so complain */ errmsg = libpq_gettext("insufficient data in \"D\" message"); + status = -1; goto advance_and_error; } if (tupnfields != nfields) { errmsg = libpq_gettext("unexpected field count in \"D\" message"); + status = -1; goto advance_and_error; } @@ -705,7 +776,7 @@ getAnotherTuple(PGconn *conn, int msgLength) nfields * sizeof(PGdataValue)); if (!rowbuf) { - errmsg = NULL; /* means "out of memory", see below */ + status = -2; goto advance_and_error; } conn->rowBuf = rowbuf; @@ -720,6 +791,7 @@ getAnotherTuple(PGconn *conn, int msgLength) { /* We should not run out of data here, so complain */ errmsg = libpq_gettext("insufficient data in \"D\" message"); + status = -1; goto advance_and_error; } rowbuf[i].len = vlen; @@ -738,6 +810,7 @@ getAnotherTuple(PGconn *conn, int msgLength) { /* We should not run out of data here, so complain */ errmsg = libpq_gettext("insufficient data in \"D\" message"); + status = -1; goto advance_and_error; } } @@ -747,6 +820,7 @@ getAnotherTuple(PGconn *conn, int msgLength) if (conn->inCursor != conn->inStart + 5 + msgLength) { errmsg = libpq_gettext("extraneous data in \"D\" message"); + status = -1; goto advance_and_error; } @@ -756,7 +830,7 @@ getAnotherTuple(PGconn *conn, int msgLength) /* Process the collected row */ errmsg = NULL; if (pqRowProcessor(conn, &errmsg)) - return 0; /* normal, successful exit */ + return status; /* normal, successful exit */ goto set_error_result; /* pqRowProcessor failed, report it */ @@ -773,23 +847,19 @@ set_error_result: pqClearAsyncResult(conn); /* - * If preceding code didn't provide an error message, assume "out of - * memory" was meant. The advantage of having this special case is that - * freeing the old result first greatly improves the odds that gettext() - * will succeed in providing a translation. + * If preceding code has set an error message, save it. */ - if (!errmsg) - errmsg = libpq_gettext("out of memory for query result"); - - printfPQExpBuffer(&conn->errorMessage, "%s\n", errmsg); - pqSaveErrorResult(conn); + if (errmsg != NULL) + printfPQExpBuffer(&conn->errorMessage, "%s\n", errmsg); /* - * Return zero to allow input parsing to continue. Subsequent "D" - * messages will be ignored until we get to end of data, since an error - * result is already set up. + * Return -1 to allow input parsing to continue in case of error. + * Subsequent "D" messages will be ignored until we get to end of + * data in this case, since an error result is already set up. If + * an out-of-memory happens, bail out immediately though and let + * the caller know that. */ - return 0; + return status; } @@ -798,7 +868,8 @@ set_error_result: * This is possible in several places, so we break it out as a subroutine. * Entry: 'E' or 'N' message type and length have already been consumed. * Exit: returns 0 if successfully consumed message. - * returns EOF if not enough data. + * returns -1 if not enough data. + * returns -2 in case of out-of-memory. */ int pqGetErrorNotice3(PGconn *conn, bool isError) @@ -809,6 +880,7 @@ pqGetErrorNotice3(PGconn *conn, bool isError) const char *val; const char *querytext = NULL; int querypos = 0; + int status = 0; /* * Since the fields might be pretty long, we create a temporary @@ -825,7 +897,10 @@ pqGetErrorNotice3(PGconn *conn, bool isError) */ res = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY); if (!res) + { + status = -2; goto fail; + } res->resultStatus = isError ? PGRES_FATAL_ERROR : PGRES_NONFATAL_ERROR; /* @@ -834,11 +909,17 @@ pqGetErrorNotice3(PGconn *conn, bool isError) for (;;) { if (pqGetc(&id, conn)) + { + status = -1; goto fail; + } if (id == '\0') break; /* terminator found */ if (pqGets(&workBuf, conn)) + { + status = -1; goto fail; + } pqSaveMessageField(res, id, workBuf.data); } @@ -968,7 +1049,10 @@ pqGetErrorNotice3(PGconn *conn, bool isError) { res->errMsg = pqResultStrdup(res, workBuf.data); if (!res->errMsg) + { + status = -1; goto fail; + } pqClearAsyncResult(conn); conn->result = res; appendPQExpBufferStr(&conn->errorMessage, workBuf.data); @@ -983,12 +1067,12 @@ pqGetErrorNotice3(PGconn *conn, bool isError) } termPQExpBuffer(&workBuf); - return 0; + return status; fail: PQclear(res); termPQExpBuffer(&workBuf); - return EOF; + return status; } /* @@ -1309,6 +1393,11 @@ getNotify(PGconn *conn) * CopyBothResponse message * * parseInput already read the message type and length. + * Returns: 0 in case of success. + * -1 if there is not enough data. In this case parsing should + * continue later on. + * -2 in case of an OOM error. In this case parsing should be + * suspended immediately. */ static int getCopyStart(PGconn *conn, ExecStatusType copytype) @@ -1316,17 +1405,27 @@ getCopyStart(PGconn *conn, ExecStatusType copytype) PGresult *result; int nfields; int i; + int res = 0; result = PQmakeEmptyPGresult(conn, copytype); if (!result) + { + res = -2; goto failure; + } if (pqGetc(&conn->copy_is_binary, conn)) + { + res = -1; goto failure; + } result->binary = conn->copy_is_binary; /* the next two bytes are the number of fields */ if (pqGetInt(&(result->numAttributes), 2, conn)) + { + res = -1; goto failure; + } nfields = result->numAttributes; /* allocate space for the attribute descriptors */ @@ -1335,7 +1434,10 @@ getCopyStart(PGconn *conn, ExecStatusType copytype) result->attDescs = (PGresAttDesc *) pqResultAlloc(result, nfields * sizeof(PGresAttDesc), TRUE); if (!result->attDescs) + { + res = -2; goto failure; + } MemSet(result->attDescs, 0, nfields * sizeof(PGresAttDesc)); } @@ -1344,7 +1446,10 @@ getCopyStart(PGconn *conn, ExecStatusType copytype) int format; if (pqGetInt(&format, 2, conn)) + { + res = -1; goto failure; + } /* * Since pqGetInt treats 2-byte integers as unsigned, we need to @@ -1356,11 +1461,12 @@ getCopyStart(PGconn *conn, ExecStatusType copytype) /* Success! */ conn->result = result; - return 0; + return res; failure: - PQclear(result); - return EOF; + if (result) + PQclear(result); + return res; } /* @@ -1457,8 +1563,13 @@ getCopyDataMessage(PGconn *conn) return 0; break; case 'N': /* NOTICE */ - if (pqGetErrorNotice3(conn, false)) - return 0; + { + int res = pqGetErrorNotice3(conn, false); + if (res == -1) + return 0; + else if (res == -2) + return -2; + } break; case 'S': /* ParameterStatus */ if (getParameterStatus(conn)) @@ -1922,9 +2033,14 @@ pqFunctionCall3(PGconn *conn, Oid fnid, status = PGRES_COMMAND_OK; break; case 'E': /* error return */ - if (pqGetErrorNotice3(conn, true)) - continue; - status = PGRES_FATAL_ERROR; + { + int res = pqGetErrorNotice3(conn, true); + if (res == -1) + continue; + else if (res == -2) + return NULL; + status = PGRES_FATAL_ERROR; + } break; case 'A': /* notify message */ /* handle notify and go back to processing return values */ @@ -1933,8 +2049,14 @@ pqFunctionCall3(PGconn *conn, Oid fnid, break; case 'N': /* notice */ /* handle notice and go back to processing return values */ - if (pqGetErrorNotice3(conn, false)) - continue; + { + int res = pqGetErrorNotice3(conn, false); + if (res == -1) + continue; + else if (res == -2) + return NULL; + status = PGRES_FATAL_ERROR; + } break; case 'Z': /* backend is ready for new query */ if (getReadyForQuery(conn)) diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 2175957..235af89 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -559,7 +559,7 @@ extern PostgresPollingStatusType pqSetenvPoll(PGconn *conn); extern char *pqBuildStartupPacket2(PGconn *conn, int *packetlen, const PQEnvironmentOption *options); -extern void pqParseInput2(PGconn *conn); +extern int pqParseInput2(PGconn *conn); extern int pqGetCopyData2(PGconn *conn, char **buffer, int async); extern int pqGetline2(PGconn *conn, char *s, int maxlen); extern int pqGetlineAsync2(PGconn *conn, char *buffer, int bufsize); @@ -573,7 +573,7 @@ extern PGresult *pqFunctionCall2(PGconn *conn, Oid fnid, extern char *pqBuildStartupPacket3(PGconn *conn, int *packetlen, const PQEnvironmentOption *options); -extern void pqParseInput3(PGconn *conn); +extern int pqParseInput3(PGconn *conn); extern int pqGetErrorNotice3(PGconn *conn, bool isError); extern int pqGetCopyData3(PGconn *conn, char **buffer, int async); extern int pqGetline3(PGconn *conn, char *s, int maxlen); -- 2.4.1