ZeroPath at Black Hat USA 2026

PostgreSQL libpq CVE-2026-6477: Brief Summary of a Client Side Stack Buffer Overwrite via Large Object Functions

A short review of CVE-2026-6477, a high severity stack buffer overwrite in PostgreSQL's libpq library that allows a malicious server superuser to corrupt client memory in psql and pg_dump. Includes patch analysis and affected version details.

CVE Analysis

8 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-05-14

PostgreSQL libpq CVE-2026-6477: Brief Summary of a Client Side Stack Buffer Overwrite via Large Object Functions
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

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 client
  • lo_read(): Reads data from a large object descriptor into a caller provided buffer
  • lo_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:

  1. A malicious actor controls a PostgreSQL server (or has superuser privileges on one).
  2. A victim connects to this server using an unpatched psql client and issues a \lo_export command, or an automated pipeline runs pg_dump against the server.
  3. Both \lo_export and pg_dump internally call lo_read(), which invokes PQfn() with result_is_int=0.
  4. The malicious server responds with a large object data payload that exceeds the size of the stack buffer allocated by the client.
  5. PQfn() writes the entire server response into the buffer without any bounds checking, overwriting adjacent stack memory.
  6. 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 BranchVulnerable VersionsFixed Version
18Before 18.418.4
17Before 17.1017.10
16Before 16.1416.14
15Before 15.1815.18
14Before 14.2314.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

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Jira
  • Linear
  • Slack
  • Security Compass
Security magnifying glass visualization