Skip to content

Commit 514b4c1

Browse files
committed
Improve libpq's handling of OOM during error message construction.
Commit ffa2e46 changed libpq so that multiple error reports occurring during one operation (a connection attempt or query) are accumulated in conn->errorMessage, where before new ones usually replaced any prior error. At least in theory, that makes us more vulnerable to running out of memory for the errorMessage buffer. If it did happen, the user would be left with just an empty-string error report, which is pretty unhelpful. We can improve this by relying on pqexpbuffer.c's existing "broken buffer" convention to track whether we've hit OOM for the current operation's error string, and then substituting a constant "out of memory" string in the small number of places where the errorMessage is read out. While at it, apply the same method to similar OOM cases in pqInternalNotice and pqGetErrorNotice3. Back-patch to v14 where ffa2e46 came in. In principle this could go back further; but in view of the lack of field reports, the hazard seems negligible in older branches. Discussion: https://postgr.es/m/530153.1627425648@sss.pgh.pa.us
1 parent b35a67b commit 514b4c1

File tree

4 files changed

+48
-19
lines changed

4 files changed

+48
-19
lines changed

src/interfaces/libpq/fe-connect.c

+8
Original file line numberDiff line numberDiff line change
@@ -6739,6 +6739,14 @@ PQerrorMessage(const PGconn *conn)
67396739
if (!conn)
67406740
return libpq_gettext("connection pointer is NULL\n");
67416741

6742+
/*
6743+
* The errorMessage buffer might be marked "broken" due to having
6744+
* previously failed to allocate enough memory for the message. In that
6745+
* case, tell the application we ran out of memory.
6746+
*/
6747+
if (PQExpBufferBroken(&conn->errorMessage))
6748+
return libpq_gettext("out of memory\n");
6749+
67426750
return conn->errorMessage.data;
67436751
}
67446752

src/interfaces/libpq/fe-exec.c

+28-14
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
191191
/* non-error cases */
192192
break;
193193
default:
194-
pqSetResultError(result, conn->errorMessage.data);
194+
pqSetResultError(result, &conn->errorMessage);
195195
break;
196196
}
197197

@@ -662,14 +662,28 @@ pqResultStrdup(PGresult *res, const char *str)
662662
* assign a new error message to a PGresult
663663
*/
664664
void
665-
pqSetResultError(PGresult *res, const char *msg)
665+
pqSetResultError(PGresult *res, PQExpBuffer errorMessage)
666666
{
667+
char *msg;
668+
667669
if (!res)
668670
return;
669-
if (msg && *msg)
670-
res->errMsg = pqResultStrdup(res, msg);
671+
672+
/*
673+
* We handle two OOM scenarios here. The errorMessage buffer might be
674+
* marked "broken" due to having previously failed to allocate enough
675+
* memory for the message, or it might be fine but pqResultStrdup fails
676+
* and returns NULL. In either case, just make res->errMsg point directly
677+
* at a constant "out of memory" string.
678+
*/
679+
if (!PQExpBufferBroken(errorMessage))
680+
msg = pqResultStrdup(res, errorMessage->data);
681+
else
682+
msg = NULL;
683+
if (msg)
684+
res->errMsg = msg;
671685
else
672-
res->errMsg = NULL;
686+
res->errMsg = libpq_gettext("out of memory\n");
673687
}
674688

675689
/*
@@ -852,19 +866,19 @@ pqInternalNotice(const PGNoticeHooks *hooks, const char *fmt,...)
852866
/* XXX should provide a SQLSTATE too? */
853867

854868
/*
855-
* Result text is always just the primary message + newline. If we can't
856-
* allocate it, don't bother invoking the receiver.
869+
* Result text is always just the primary message + newline. If we can't
870+
* allocate it, substitute "out of memory", as in pqSetResultError.
857871
*/
858872
res->errMsg = (char *) pqResultAlloc(res, strlen(msgBuf) + 2, false);
859873
if (res->errMsg)
860-
{
861874
sprintf(res->errMsg, "%s\n", msgBuf);
875+
else
876+
res->errMsg = libpq_gettext("out of memory\n");
862877

863-
/*
864-
* Pass to receiver, then free it.
865-
*/
866-
res->noticeHooks.noticeRec(res->noticeHooks.noticeRecArg, res);
867-
}
878+
/*
879+
* Pass to receiver, then free it.
880+
*/
881+
res->noticeHooks.noticeRec(res->noticeHooks.noticeRecArg, res);
868882
PQclear(res);
869883
}
870884

@@ -2122,7 +2136,7 @@ PQgetResult(PGconn *conn)
21222136
appendPQExpBuffer(&conn->errorMessage,
21232137
libpq_gettext("PGEventProc \"%s\" failed during PGEVT_RESULTCREATE event\n"),
21242138
res->events[i].name);
2125-
pqSetResultError(res, conn->errorMessage.data);
2139+
pqSetResultError(res, &conn->errorMessage);
21262140
res->resultStatus = PGRES_FATAL_ERROR;
21272141
break;
21282142
}

src/interfaces/libpq/fe-protocol3.c

+11-4
Original file line numberDiff line numberDiff line change
@@ -967,12 +967,12 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
967967
if (isError)
968968
{
969969
if (res)
970-
res->errMsg = pqResultStrdup(res, workBuf.data);
970+
pqSetResultError(res, &workBuf);
971971
pqClearAsyncResult(conn); /* redundant, but be safe */
972972
conn->result = res;
973973
if (PQExpBufferDataBroken(workBuf))
974974
appendPQExpBufferStr(&conn->errorMessage,
975-
libpq_gettext("out of memory"));
975+
libpq_gettext("out of memory\n"));
976976
else
977977
appendPQExpBufferStr(&conn->errorMessage, workBuf.data);
978978
}
@@ -981,8 +981,15 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
981981
/* if we couldn't allocate the result set, just discard the NOTICE */
982982
if (res)
983983
{
984-
/* We can cheat a little here and not copy the message. */
985-
res->errMsg = workBuf.data;
984+
/*
985+
* We can cheat a little here and not copy the message. But if we
986+
* were unlucky enough to run out of memory while filling workBuf,
987+
* insert "out of memory", as in pqSetResultError.
988+
*/
989+
if (PQExpBufferDataBroken(workBuf))
990+
res->errMsg = libpq_gettext("out of memory\n");
991+
else
992+
res->errMsg = workBuf.data;
986993
if (res->noticeHooks.noticeRec != NULL)
987994
res->noticeHooks.noticeRec(res->noticeHooks.noticeRecArg, res);
988995
PQclear(res);

src/interfaces/libpq/libpq-int.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,7 @@ extern pgthreadlock_t pg_g_threadlock;
637637

638638
/* === in fe-exec.c === */
639639

640-
extern void pqSetResultError(PGresult *res, const char *msg);
640+
extern void pqSetResultError(PGresult *res, PQExpBuffer errorMessage);
641641
extern void *pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary);
642642
extern char *pqResultStrdup(PGresult *res, const char *str);
643643
extern void pqClearAsyncResult(PGconn *conn);

0 commit comments

Comments
 (0)