ZeroPath at Black Hat USA 2026

PostgreSQL CVE-2026-6473: Brief Summary of Integer Wraparound Leading to Out of Bounds Write

A brief summary of CVE-2026-6473, a high severity integer wraparound vulnerability in PostgreSQL that allows undersized memory allocations and out of bounds writes, with patch details covering the systemic fix introduced across all supported branches.

CVE Analysis

9 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-05-14

PostgreSQL CVE-2026-6473: Brief Summary of Integer Wraparound Leading to Out of Bounds Write
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

An integer wraparound in PostgreSQL's memory allocation macros means that a low privileged database user, by supplying carefully sized inputs to several built in features, can force the server to allocate a tiny buffer and then write far beyond it. The result ranges from an immediate segmentation fault to potential arbitrary code execution as the operating system user running the database process, earning this vulnerability a CVSS 3.1 base score of 8.8.

The flaw, tracked as CVE-2026-6473, was patched on May 12, 2026, across all five supported major version branches (14 through 18). Because the attack is network reachable, requires only low privileges, and touches multiple server components, it warrants prompt attention from any team running PostgreSQL in production.

Technical Information

Root Cause: Unchecked Integer Multiplication in Allocation Macros

The fundamental problem is that PostgreSQL's convenience allocation macros performed raw, unchecked multiplication to compute buffer sizes. The macros palloc_array(type, count) and related variants expanded to expressions like palloc(sizeof(type) * (count)). On 32 bit builds, where size_t is 32 bits, a sufficiently large attacker controlled count value causes the multiplication to silently wrap around to a small value. The server then allocates a tiny buffer, and the calling code writes well past its end.

Before the fix, the macros looked like this:

#define palloc_array(type, count) ((type *) palloc(sizeof(type) * (count))) #define repalloc_array(pointer, type, count) ((type *) repalloc(pointer, sizeof(type) * (count)))

There is no overflow check whatsoever. If sizeof(type) * count wraps around on a 32 bit size_t, the allocator receives a small number and returns a correspondingly small buffer.

While the hazard is most acute on 32 bit builds, the release notes explicitly state the problem exists "primarily, though not exclusively" on those platforms.

Affected Components and Attack Surface

The vulnerability manifests across several distinct PostgreSQL features and extension modules:

contrib/ltree: Values within the lquery type containing more than 64K items cause internal overflows, which can potentially result in stack smashes or incorrect answers. Additionally, parsing of the ltxtquery type did not check for the overflow of 16 bit fields, allowing the construction of an invalid query tree that can crash the server during execution.

contrib/intarray: The parsing of the query_int type similarly failed to check for 16 bit field overflows, leading to invalid query trees and server crashes.

ts_headline function: The function failed to reject over length options. Specifically, the StartSel, StopSel, and FragmentDelimiter strings must not exceed 32Kb in length. Providing an over length value typically crashes the server.

Attack Flow

An attacker exploiting this vulnerability would follow a general sequence:

  1. Establish a low privilege database connection. The CVSS vector (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) confirms that network access and low privileges are sufficient; no user interaction is required.

  2. Craft an input that triggers integer wraparound. For the allocation macro path, this means supplying a count value large enough that sizeof(type) * count wraps around to a small number. For the contrib/intarray or contrib/ltree paths, this means constructing a deeply nested or excessively long query expression that causes an int16 offset to overflow.

  3. Trigger the vulnerable code path. This could be a query using ltree operators, an intarray query, or a call to ts_headline with oversized option strings.

  4. Exploit the out of bounds write. The undersized buffer allocation followed by a write beyond its bounds corrupts adjacent heap memory. At minimum, this causes a segmentation fault and server crash (denial of service). With careful control of the overwritten data, an attacker could potentially redirect execution flow and achieve arbitrary code execution as the operating system user running the PostgreSQL process.

The Int16 Overflow Vector in Detail

Both contrib/intarray's query_int type and contrib/ltree's ltxtquery type represent parsed boolean expressions as postfix arrays of ITEM structures. Each binary operator node stores a left field, an int16, that holds the relative offset to its left operand. While the overall number of nodes was bounded by MaxAllocSize, the offset between two nodes could exceed the int16 range (plus or minus 32767) for deep, left skewed trees. This is a distinct overflow vector from the allocation macro issue, but both contribute to the same CVE.

Patch Information

The PostgreSQL Global Development Group addressed CVE-2026-6473 with two complementary commits, both authored by Tom Lane and committed by Noah Misch on May 11, 2026. The fixes were published on May 12, 2026, in PostgreSQL versions 18.4, 17.10, 16.14, 15.18, and 14.23, backpatched all the way to version 14.

Commit 1: Overflow Safe Memory Allocation Macros (e1c30458a)

This is the architectural heart of the fix. Rather than hunting down every at risk call site individually, the patch took a systemic approach. It introduced a new family of overflow checked allocation functions:

  • palloc_mul(Size s1, Size s2) equivalent to palloc(mul_size(s1, s2))
  • palloc0_mul(Size s1, Size s2) equivalent to palloc0(mul_size(s1, s2))
  • palloc_mul_extended(Size s1, Size s2, int flags)
  • repalloc_mul(void *p, Size s1, Size s2)
  • repalloc_mul_extended(void *p, Size s1, Size s2, int flags)

Each of these internally uses pg_mul_size_overflow() (from common/int.h) to perform checked multiplication. If the product overflows, the function calls mul_size_error(), declared pg_noreturn and pg_noinline, which raises an ERRCODE_PROGRAM_LIMIT_EXCEEDED error, cleanly aborting the operation.

The macros were then rewritten. After the patch:

#define palloc_array(type, count) ((type *) palloc_mul(sizeof(type), count)) #define repalloc_array(pointer, type, count) ((type *) repalloc_mul(pointer, sizeof(type), count))

By pushing the two size operands into a function that checks for overflow before calling the actual allocator, every single user of these macros now gets automatic protection with no caller changes needed.

The existing add_size() and mul_size() functions were also relocated from src/backend/storage/ipc/shmem.c (where they were specific to shared memory accounting) into the general purpose src/backend/utils/mmgr/mcxt.c, with declarations moved to src/include/utils/palloc.h. Their implementations were upgraded to use pg_add_size_overflow() and pg_mul_size_overflow() from common/int.h instead of ad hoc manual overflow checks. For frontend code (fe_memutils.c/h), parallel equivalents (pg_malloc_mul, pg_malloc0_mul, pg_realloc_mul, etc.) were added, with the additional guard of rejecting results larger than SIZE_MAX / 2.

Commit 2: Int16 Overflow Guards in contrib/intarray and contrib/ltree (c4d04cc48)

This companion commit addresses the separate integer overflow vector in the two contrib modules. The fix refactored the findoprnd() function in both modules. Previously, it calculated offsets without bounds checking and stored them directly. The patched version computes the delta and explicitly validates it against PG_INT16_MIN (for intarray, which uses negative offsets) or PG_INT16_MAX (for ltree, which uses positive offsets). If the offset is out of range, the function reports a soft error:

// In contrib/intarray/_int_bool.c (binary operator case): delta = *pos - mypos; if (unlikely(delta < PG_INT16_MIN)) ereturn(state->escontext, false, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("query_int expression is too complex"))); ptr[mypos].left = (int16) delta;

Regression tests were added for both modules, constructing queries with 17,000 AND chained operands to trigger the overflow, confirming that the new bounds check fires correctly.

Affected Systems and Versions

The following PostgreSQL versions are affected:

Major VersionAffected VersionsFixed Version
18Versions before 18.418.4
17Versions before 17.1017.10
16Versions before 16.1416.14
15Versions before 15.1815.18
14Versions before 14.2314.23

All fixes were released on May 12, 2026. The vulnerability is particularly acute on 32 bit builds, where size_t is 32 bits and the integer wraparound in allocation size calculations is easier to trigger. Deployments using the contrib/ltree, contrib/intarray extensions, or the ts_headline function have additional specific attack surface.

Vendor Security History

The May 2026 patch release addressed 11 security vulnerabilities and over 60 bugs across all supported PostgreSQL branches. The PostgreSQL project's coordinated disclosure and simultaneous multi branch release demonstrates a mature vulnerability handling process. The project publicly credited multiple independent security researchers for reporting the various facets of CVE-2026-6473, including Xint Code, Bruce Dang, Sven Klemm, and Pavel Kohout, among others.

References

Detect & fix
what others miss

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