Introduction
A long standing unsafe API pattern in PostgreSQL's libpq client library has finally been addressed after researchers demonstrated that a malicious server superuser could overwrite client stack memory during routine large object operations. For any organization running psql or pg_dump against databases they do not fully control, CVE-2026-6477 represents a direct path from a compromised server to arbitrary code execution on the client machine.
The vulnerability, scored at CVSS 8.8, affects the PQfn() function that underpins large object handling in libpq. Because PostgreSQL is the most popular database among developers according to the 2025 Stack Overflow Developer Survey (47 percent want to use it, 66 percent of current users want to continue), the blast radius of vulnerable libpq binaries across CI pipelines, backup infrastructure, and administrative workstations is substantial.
Technical Information
The Root Cause: An Unfixable Public API
The core of CVE-2026-6477 lies in the PQfn() function within libpq (src/interfaces/libpq/fe-exec.c). When called with the parameter result_is_int=0 (indicating a non integer result type), PQfn() is never told how large the output buffer is. Without this size parameter, the function cannot verify that the data returned by the server will fit into the allocated memory. It simply writes whatever the server sends into result_buf with no bounds check.
This is conceptually identical to the infamous gets() function in C, which was removed from the C11 standard precisely because it stores arbitrary length input into a buffer of unspecified size. The PostgreSQL advisory explicitly draws this parallel: "Like gets(), PQfn(..., result_is_int=0, ...) stores arbitrary-length, server-determined data into a buffer of unspecified size."
Vulnerable Large Object Functions
The dangerous PQfn() code path is exposed through several libpq large object functions:
lo_export(): Exports a large object to a file on the clientlo_read(): Reads data from a large object descriptor into a caller provided bufferlo_lseek64(): Seeks within a large object (64 bit variant for objects exceeding 2GB)lo_tell64(): Returns the current position within a large object (64 bit variant)
Each of these functions calls PQfn() with result_is_int=0, passing a stack allocated buffer whose size is never communicated to the underlying protocol handler.
Although lo_read() rejects length values larger than INT_MAX at the application layer, this check is irrelevant to the vulnerability. The issue is not what the client requests but what the server returns. A malicious server can respond with an arbitrarily large payload regardless of the requested read size, and PQfn() will dutifully write it all into the undersized buffer.
Attack Flow
The exploitation scenario proceeds as follows:
- A malicious actor controls a PostgreSQL server (or has superuser privileges on one).
- A victim connects to this server using an unpatched
psqlclient and issues a\lo_exportcommand, or an automated pipeline runspg_dumpagainst the server. - Both
\lo_exportandpg_dumpinternally calllo_read(), which invokesPQfn()withresult_is_int=0. - The malicious server responds with a large object data payload that exceeds the size of the stack buffer allocated by the client.
PQfn()writes the entire server response into the buffer without any bounds checking, overwriting adjacent stack memory.- The attacker achieves stack memory corruption on the client machine, potentially leading to arbitrary code execution.
The CVSS vector AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H captures this scenario: network accessible, low complexity, no privileges required on the attacker's side (they control the server), but user interaction is required (the victim must connect to the malicious server). The impact on confidentiality, integrity, and availability is rated high across the board.
Why This Is a Client Side Vulnerability
It is worth emphasizing that this is not a server vulnerability. The overflow occurs entirely within the client process. The server is the attacker; the client is the victim. This inverts the typical threat model for database vulnerabilities, where the server is usually the target. Organizations that connect administrative tools to third party, cloud hosted, or otherwise untrusted PostgreSQL instances are at particular risk.
Patch Information
The PostgreSQL Global Development Group addressed CVE-2026-6477 in a coordinated release on May 14, 2026, shipping fixes across PostgreSQL versions 18.4, 17.10, 16.14, 15.18, and 14.23 (backpatched through version 14). The fix landed in commit 8ac723b2b, authored by Nathan Bossart and committed by Noah Misch.
Because changing the PQfn() function signature would break its public API contract, the patch takes a pragmatic two pronged approach: deprecate the public function and introduce a safe internal replacement.
New Internal Function: PQnfn()
In src/interfaces/libpq/fe-exec.c, the patch introduces a private function PQnfn() that mirrors PQfn() but accepts an additional buf_size parameter:
PGresult * PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size, int *result_len, int result_is_int, const PQArgBlock *args, int nargs)
The original PQfn() is preserved for backward compatibility but now simply delegates to PQnfn() with buf_size set to -1, meaning no size verification. This preserves the (unsafe) old behavior for any third party callers while allowing all internal code to move to the safe path.
Buffer Overflow Guard in the Protocol Layer
In src/interfaces/libpq/fe-protocol3.c, the pqFunctionCall3() function also gains the new buf_size parameter. A critical new check is inserted right before data is copied into the result buffer:
if (buf_size != -1 && *actual_result_len > buf_size) { appendPQExpBufferStr(&conn->errorMessage, libpq_gettext("server returned too much data\n")); handleSyncLoss(conn, id, *actual_result_len); return pqPrepareAsyncResult(conn); }
This is the heart of the fix. If the server responds with more data than the buffer can hold, the connection is declared lost and the call aborts safely. The overrun never happens.
Migration of In Tree Callers
The only in tree callers of the old PQfn() were the libpq large object functions. Each was updated in src/interfaces/libpq/fe-lobj.c to call PQnfn() instead, now passing the correct buffer size. For example, lo_read() passes the caller supplied len, while lo_lseek64() and lo_tell64() pass sizeof(retval) for their 8 byte return values.
Documentation Update
In doc/src/sgml/libpq.sgml, the old tip about PQfn() being "somewhat obsolete" was replaced with a warning block explicitly stating that the interface is unsafe and should not be used, noting the buffer overwrite risk when result_is_int is 0.
In summary, the patch isolates the danger: the unfixable public API is marked deprecated and left in place for compatibility, while all PostgreSQL internal code paths are moved to the new PQnfn() function that enforces proper buffer size checks at the protocol layer.
Affected Systems and Versions
The following PostgreSQL versions are affected:
| Major Branch | Vulnerable Versions | Fixed Version |
|---|---|---|
| 18 | Before 18.4 | 18.4 |
| 17 | Before 17.10 | 17.10 |
| 16 | Before 16.14 | 16.14 |
| 15 | Before 15.18 | 15.18 |
| 14 | Before 14.23 | 14.23 |
The vulnerability specifically affects the client side libpq library and any application or tool that links against it. The most prominent affected tools are psql (via the \lo_export meta command) and pg_dump (via lo_read()). Any custom application that uses libpq large object functions (lo_export(), lo_read(), lo_lseek64(), lo_tell64()) or calls PQfn() directly with result_is_int=0 is also vulnerable.
Administrators must ensure that all client binaries link to the updated libpq library to fully remediate the risk. Patching the server alone is insufficient because this is a client side vulnerability.
Vendor Security History
The PostgreSQL project maintains a mature and coordinated security response process. The May 2026 update cycle addressed a total of 11 security vulnerabilities alongside over 60 bug fixes. The project consistently delivers simultaneous patches across all supported major versions, ensuring comprehensive coverage for its user base. CVE-2026-6477 was discovered and responsibly reported by security researchers Yu Kunpeng and Martin Heistermann.
References
- PostgreSQL Security Advisory: CVE-2026-6477
- PostgreSQL 18.4, 17.10, 16.14, 15.18, and 14.23 Released!
- Documentation: 18: E.1. Release 18.4
- Documentation: 17: Release 17.10
- Patch Commit 8ac723b2b
- PostgreSQL Documentation: 18: 33.3. Client Interfaces (Large Objects)
- NVD Entry: CVE-2026-6477
- PostgreSQL on Wikipedia
- Stack Overflow 2025 Developer Survey



