Skip to content

Commit 11d5d0b

Browse files
nathan-bossartRucha Kulkarni
authored andcommitted
Mark PQfn() unsafe and fix overrun in frontend LO interface.
When result_is_int is set to 0, PQfn() cannot validate that the result fits in result_buf, so it will write data beyond the end of the buffer when the server returns more data than requested. Since this function is insecurable and obsolete, add a warning to the top of the pertinent documentation advising against its use. The only in-tree caller of PQfn() is the frontend large object interface. To fix that, add a buf_size parameter to pqFunctionCall3() that is used to protect against overruns, and use it in a private version of PQfn() that also accepts a buf_size parameter. Reported-by: Yu Kunpeng <yu443940816@live.com> Reported-by: Martin Heistermann <martin.heistermann@unibe.ch> Author: Nathan Bossart <nathandbossart@gmail.com> Reviewed-by: Noah Misch <noah@leadboat.com> Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us> Reviewed-by: Etsuro Fujita <etsuro.fujita@gmail.com> Security: CVE-2026-6477 Backpatch-through: 14 (cherry picked from commit 614474996a48b7fc7f83faf9a940600041a80d67)
1 parent 40b12c6 commit 11d5d0b

5 files changed

Lines changed: 46 additions & 12 deletions

File tree

doc/src/sgml/libpq.sgml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6026,15 +6026,20 @@ int PQrequestCancel(PGconn *conn);
60266026
to send simple function calls to the server.
60276027
</para>
60286028

6029-
<tip>
6029+
<warning>
60306030
<para>
6031-
This interface is somewhat obsolete, as one can achieve similar
6031+
This interface is unsafe and should not be used. When
6032+
<parameter>result_is_int</parameter> is set to <literal>0</literal>,
6033+
<function>PQfn</function> may write data beyond the end of
6034+
<parameter>result_buf</parameter>, regardless of whether the buffer has
6035+
enough space for the requested number of bytes. Furthermore, it is
6036+
obsolete, as one can achieve similar
60326037
performance and greater functionality by setting up a prepared
60336038
statement to define the function call. Then, executing the statement
60346039
with binary transmission of parameters and results substitutes for a
60356040
fast-path function call.
60366041
</para>
6037-
</tip>
6042+
</warning>
60386043

60396044
<para>
60406045
The function <function id="libpq-PQfn">PQfn</function><indexterm><primary>PQfn</primary></indexterm>

src/interfaces/libpq/fe-exec.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2870,6 +2870,20 @@ PQfn(PGconn *conn,
28702870
int result_is_int,
28712871
const PQArgBlock *args,
28722872
int nargs)
2873+
{
2874+
return PQnfn(conn, fnid, result_buf, -1, result_len,
2875+
result_is_int, args, nargs);
2876+
}
2877+
2878+
/*
2879+
* PQnfn
2880+
* Private version of PQfn() with verification that returned data fits in
2881+
* result_buf when result_is_int == 0. Setting buf_size to -1 disables
2882+
* this verification.
2883+
*/
2884+
PGresult *
2885+
PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size, int *result_len,
2886+
int result_is_int, const PQArgBlock *args, int nargs)
28732887
{
28742888
*result_len = 0;
28752889

@@ -2898,7 +2912,7 @@ PQfn(PGconn *conn,
28982912
}
28992913

29002914
return pqFunctionCall3(conn, fnid,
2901-
result_buf, result_len,
2915+
result_buf, buf_size, result_len,
29022916
result_is_int,
29032917
args, nargs);
29042918
}

src/interfaces/libpq/fe-lobj.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,8 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len)
271271
argv[1].len = 4;
272272
argv[1].u.integer = (int) len;
273273

274-
res = PQfn(conn, conn->lobjfuncs->fn_lo_read,
275-
(void *) buf, &result_len, 0, argv, 2);
274+
res = PQnfn(conn, conn->lobjfuncs->fn_lo_read,
275+
(void *) buf, len, &result_len, 0, argv, 2);
276276
if (PQresultStatus(res) == PGRES_COMMAND_OK)
277277
{
278278
PQclear(res);
@@ -412,8 +412,8 @@ lo_lseek64(PGconn *conn, int fd, pg_int64 offset, int whence)
412412
argv[2].len = 4;
413413
argv[2].u.integer = whence;
414414

415-
res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek64,
416-
(void *) &retval, &result_len, 0, argv, 3);
415+
res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64,
416+
(void *) &retval, sizeof(retval), &result_len, 0, argv, 3);
417417
if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8)
418418
{
419419
PQclear(res);
@@ -566,8 +566,8 @@ lo_tell64(PGconn *conn, int fd)
566566
argv[0].len = 4;
567567
argv[0].u.integer = fd;
568568

569-
res = PQfn(conn, conn->lobjfuncs->fn_lo_tell64,
570-
(void *) &retval, &result_len, 0, argv, 1);
569+
res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64,
570+
(void *) &retval, sizeof(retval), &result_len, 0, argv, 1);
571571
if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8)
572572
{
573573
PQclear(res);

src/interfaces/libpq/fe-protocol3.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2000,7 +2000,7 @@ pqEndcopy3(PGconn *conn)
20002000
*/
20012001
PGresult *
20022002
pqFunctionCall3(PGconn *conn, Oid fnid,
2003-
int *result_buf, int *actual_result_len,
2003+
int *result_buf, int buf_size, int *actual_result_len,
20042004
int result_is_int,
20052005
const PQArgBlock *args, int nargs)
20062006
{
@@ -2134,6 +2134,17 @@ pqFunctionCall3(PGconn *conn, Oid fnid,
21342134
}
21352135
else
21362136
{
2137+
/*
2138+
* If the server returned too much data for the
2139+
* buffer, something fishy is going on. Abandon ship.
2140+
*/
2141+
if (buf_size != -1 && *actual_result_len > buf_size)
2142+
{
2143+
libpq_append_conn_error(conn, "server returned too much data");
2144+
handleSyncLoss(conn, id, *actual_result_len);
2145+
return pqPrepareAsyncResult(conn);
2146+
}
2147+
21372148
if (pqGetnchar((char *) result_buf,
21382149
*actual_result_len,
21392150
conn))

src/interfaces/libpq/libpq-int.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,9 @@ extern int pqRowProcessor(PGconn *conn, const char **errmsgp);
718718
extern void pqCommandQueueAdvance(PGconn *conn, bool isReadyForQuery,
719719
bool gotSync);
720720
extern int PQsendQueryContinue(PGconn *conn, const char *query);
721+
extern PGresult *PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size,
722+
int *result_len, int result_is_int,
723+
const PQArgBlock *args, int nargs);
721724

722725
/* === in fe-protocol3.c === */
723726

@@ -733,7 +736,8 @@ extern int pqGetline3(PGconn *conn, char *s, int maxlen);
733736
extern int pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize);
734737
extern int pqEndcopy3(PGconn *conn);
735738
extern PGresult *pqFunctionCall3(PGconn *conn, Oid fnid,
736-
int *result_buf, int *actual_result_len,
739+
int *result_buf, int buf_size,
740+
int *actual_result_len,
737741
int result_is_int,
738742
const PQArgBlock *args, int nargs);
739743

0 commit comments

Comments
 (0)