Automated Threat Modeling

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 lets a low-privileged database user supply carefully sized inputs to several built-in features, 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. The vulnerability has 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). The attack is network reachable, requires only low privileges, and touches multiple server components, so any team running PostgreSQL in production should apply the fix promptly.

Technical Information

Root Cause: Unchecked Integer Multiplication in Allocation Macros

PostgreSQL's convenience allocation macros computed buffer sizes with raw, unchecked multiplication. 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 large enough attacker-controlled count value makes the multiplication silently wrap around to a small value. The server 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.

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

Affected Components and Attack Surface

The vulnerability shows up in several distinct PostgreSQL features and extension modules:

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

contrib/intarray: Parsing of the query_int type likewise 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. The StartSel, StopSel, and FragmentDelimiter strings must not exceed 32Kb in length, and 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, it means constructing a deeply nested or excessively long query expression that makes an int16 offset 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 write past the end of the undersized buffer 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 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. The overall number of nodes was bounded by MaxAllocSize, but for deep, left-skewed trees the offset between two nodes could exceed the int16 range (plus or minus 32767). This overflow vector is separate 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 commit is the main architectural change. Instead of hunting down every at-risk call site one by one, the patch fixes the problem systemically with 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 uses pg_mul_size_overflow() (from common/int.h) internally 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 and cleanly aborts 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))

The two size operands now go into a function that checks for overflow before it calls the actual allocator, so every single user of these macros gets the protection automatically, with no caller changes needed.

The existing add_size() and mul_size() functions also moved 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 now use pg_add_size_overflow() and pg_mul_size_overflow() from common/int.h instead of ad hoc manual overflow checks. Frontend code (fe_memutils.c/h) got parallel equivalents (pg_malloc_mul, pg_malloc0_mul, pg_realloc_mul, etc.), which also reject 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 by refactoring the findoprnd() function in both. The old version 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;

Both modules gained regression tests that build queries with 17,000 AND-chained operands to trigger the overflow and confirm 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 that use the contrib/ltree or contrib/intarray extensions, or the ts_headline function, have additional attack surface specific to those features.

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 show 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
  • Gerrit
  • CVS
  • Jira
  • Linear
  • Slack
  • Security Compass
Explore integrations
Security magnifying glass visualization