Skip to content

Commit 2b189c7

Browse files
committed
Improve handling of out-of-memory in libpq.
If an allocation fails in the main message handling loop, pqParseInput3 or pqParseInput2, it should not be treated as "not enough data available yet". Otherwise libpq will wait indefinitely for more data to arrive from the server, and gets stuck forever. This isn't a complete fix - getParamDescriptions and getCopyStart still have the same issue, but it's a step in the right direction. Michael Paquier and me. Backpatch to all supported versions.
1 parent 341b877 commit 2b189c7

File tree

2 files changed

+83
-31
lines changed

2 files changed

+83
-31
lines changed

src/interfaces/libpq/fe-protocol2.c

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -438,10 +438,17 @@ pqParseInput2(PGconn *conn)
438438
conn->result = PQmakeEmptyPGresult(conn,
439439
PGRES_COMMAND_OK);
440440
if (!conn->result)
441-
return;
441+
{
442+
printfPQExpBuffer(&conn->errorMessage,
443+
libpq_gettext("out of memory"));
444+
pqSaveErrorResult(conn);
445+
}
446+
}
447+
if (conn->result)
448+
{
449+
strlcpy(conn->result->cmdStatus, conn->workBuffer.data,
450+
CMDSTATUS_LEN);
442451
}
443-
strlcpy(conn->result->cmdStatus, conn->workBuffer.data,
444-
CMDSTATUS_LEN);
445452
checkXactStatus(conn, conn->workBuffer.data);
446453
conn->asyncStatus = PGASYNC_READY;
447454
break;
@@ -462,8 +469,16 @@ pqParseInput2(PGconn *conn)
462469
"unexpected character %c following empty query response (\"I\" message)",
463470
id);
464471
if (conn->result == NULL)
472+
{
465473
conn->result = PQmakeEmptyPGresult(conn,
466474
PGRES_EMPTY_QUERY);
475+
if (!conn->result)
476+
{
477+
printfPQExpBuffer(&conn->errorMessage,
478+
libpq_gettext("out of memory"));
479+
pqSaveErrorResult(conn);
480+
}
481+
}
467482
conn->asyncStatus = PGASYNC_READY;
468483
break;
469484
case 'K': /* secret key data from the backend */
@@ -811,14 +826,17 @@ pqGetErrorNotice2(PGconn *conn, bool isError)
811826
* Make a PGresult to hold the message. We temporarily lie about the
812827
* result status, so that PQmakeEmptyPGresult doesn't uselessly copy
813828
* conn->errorMessage.
829+
*
830+
* NB: This allocation can fail, if you run out of memory. The rest of the
831+
* function handles that gracefully, and we still try to set the error
832+
* message as the connection's error message.
814833
*/
815834
res = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY);
816-
if (!res)
817-
goto failure;
818-
res->resultStatus = isError ? PGRES_FATAL_ERROR : PGRES_NONFATAL_ERROR;
819-
res->errMsg = pqResultStrdup(res, workBuf.data);
820-
if (!res->errMsg)
821-
goto failure;
835+
if (res)
836+
{
837+
res->resultStatus = isError ? PGRES_FATAL_ERROR : PGRES_NONFATAL_ERROR;
838+
res->errMsg = pqResultStrdup(res, workBuf.data);
839+
}
822840

823841
/*
824842
* Break the message into fields. We can't do very much here, but we can
@@ -870,15 +888,22 @@ pqGetErrorNotice2(PGconn *conn, bool isError)
870888
pqClearAsyncResult(conn);
871889
conn->result = res;
872890
resetPQExpBuffer(&conn->errorMessage);
873-
appendPQExpBufferStr(&conn->errorMessage, res->errMsg);
891+
if (res && !PQExpBufferDataBroken(workBuf) && res->errMsg)
892+
appendPQExpBufferStr(&conn->errorMessage, res->errMsg);
893+
else
894+
printfPQExpBuffer(&conn->errorMessage,
895+
libpq_gettext("out of memory"));
874896
if (conn->xactStatus == PQTRANS_INTRANS)
875897
conn->xactStatus = PQTRANS_INERROR;
876898
}
877899
else
878900
{
879-
if (res->noticeHooks.noticeRec != NULL)
880-
(*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res);
881-
PQclear(res);
901+
if (res)
902+
{
903+
if (res->noticeHooks.noticeRec != NULL)
904+
(*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res);
905+
PQclear(res);
906+
}
882907
}
883908

884909
termPQExpBuffer(&workBuf);

src/interfaces/libpq/fe-protocol3.c

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,15 @@ pqParseInput3(PGconn *conn)
204204
conn->result = PQmakeEmptyPGresult(conn,
205205
PGRES_COMMAND_OK);
206206
if (!conn->result)
207-
return;
207+
{
208+
printfPQExpBuffer(&conn->errorMessage,
209+
libpq_gettext("out of memory"));
210+
pqSaveErrorResult(conn);
211+
}
208212
}
209-
strlcpy(conn->result->cmdStatus, conn->workBuffer.data,
210-
CMDSTATUS_LEN);
213+
if (conn->result)
214+
strlcpy(conn->result->cmdStatus, conn->workBuffer.data,
215+
CMDSTATUS_LEN);
211216
conn->asyncStatus = PGASYNC_READY;
212217
break;
213218
case 'E': /* error return */
@@ -226,7 +231,11 @@ pqParseInput3(PGconn *conn)
226231
conn->result = PQmakeEmptyPGresult(conn,
227232
PGRES_EMPTY_QUERY);
228233
if (!conn->result)
229-
return;
234+
{
235+
printfPQExpBuffer(&conn->errorMessage,
236+
libpq_gettext("out of memory"));
237+
pqSaveErrorResult(conn);
238+
}
230239
}
231240
conn->asyncStatus = PGASYNC_READY;
232241
break;
@@ -239,7 +248,11 @@ pqParseInput3(PGconn *conn)
239248
conn->result = PQmakeEmptyPGresult(conn,
240249
PGRES_COMMAND_OK);
241250
if (!conn->result)
242-
return;
251+
{
252+
printfPQExpBuffer(&conn->errorMessage,
253+
libpq_gettext("out of memory"));
254+
pqSaveErrorResult(conn);
255+
}
243256
}
244257
conn->asyncStatus = PGASYNC_READY;
245258
}
@@ -311,7 +324,11 @@ pqParseInput3(PGconn *conn)
311324
conn->result = PQmakeEmptyPGresult(conn,
312325
PGRES_COMMAND_OK);
313326
if (!conn->result)
314-
return;
327+
{
328+
printfPQExpBuffer(&conn->errorMessage,
329+
libpq_gettext("out of memory"));
330+
pqSaveErrorResult(conn);
331+
}
315332
}
316333
conn->asyncStatus = PGASYNC_READY;
317334
}
@@ -736,11 +753,14 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
736753
* Make a PGresult to hold the accumulated fields. We temporarily lie
737754
* about the result status, so that PQmakeEmptyPGresult doesn't uselessly
738755
* copy conn->errorMessage.
756+
*
757+
* NB: This allocation can fail, if you run out of memory. The rest of the
758+
* function handles that gracefully, and we still try to set the error
759+
* message as the connection's error message.
739760
*/
740761
res = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY);
741-
if (!res)
742-
goto fail;
743-
res->resultStatus = isError ? PGRES_FATAL_ERROR : PGRES_NONFATAL_ERROR;
762+
if (res)
763+
res->resultStatus = isError ? PGRES_FATAL_ERROR : PGRES_NONFATAL_ERROR;
744764

745765
/*
746766
* Read the fields and save into res.
@@ -853,20 +873,27 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
853873
*/
854874
if (isError)
855875
{
856-
res->errMsg = pqResultStrdup(res, workBuf.data);
857-
if (!res->errMsg)
858-
goto fail;
876+
if (res)
877+
res->errMsg = pqResultStrdup(res, workBuf.data);
859878
pqClearAsyncResult(conn);
860879
conn->result = res;
861-
appendPQExpBufferStr(&conn->errorMessage, workBuf.data);
880+
if (PQExpBufferDataBroken(workBuf))
881+
printfPQExpBuffer(&conn->errorMessage,
882+
libpq_gettext("out of memory"));
883+
else
884+
appendPQExpBufferStr(&conn->errorMessage, workBuf.data);
862885
}
863886
else
864887
{
865-
/* We can cheat a little here and not copy the message. */
866-
res->errMsg = workBuf.data;
867-
if (res->noticeHooks.noticeRec != NULL)
868-
(*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res);
869-
PQclear(res);
888+
/* if we couldn't allocate the result set, just discard the NOTICE */
889+
if (res)
890+
{
891+
/* We can cheat a little here and not copy the message. */
892+
res->errMsg = workBuf.data;
893+
if (res->noticeHooks.noticeRec != NULL)
894+
(*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res);
895+
PQclear(res);
896+
}
870897
}
871898

872899
termPQExpBuffer(&workBuf);

0 commit comments

Comments
 (0)