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:
-
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.
-
Craft an input that triggers integer wraparound. For the allocation macro path, this means supplying a count value large enough that
sizeof(type) * countwraps around to a small number. For thecontrib/intarrayorcontrib/ltreepaths, this means constructing a deeply nested or excessively long query expression that causes anint16offset to overflow. -
Trigger the vulnerable code path. This could be a query using
ltreeoperators, anintarrayquery, or a call tots_headlinewith oversized option strings. -
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 topalloc(mul_size(s1, s2))palloc0_mul(Size s1, Size s2)equivalent topalloc0(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 Version | Affected Versions | Fixed Version |
|---|---|---|
| 18 | Versions before 18.4 | 18.4 |
| 17 | Versions before 17.10 | 17.10 |
| 16 | Versions before 16.14 | 16.14 |
| 15 | Versions before 15.18 | 15.18 |
| 14 | Versions before 14.23 | 14.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
- PostgreSQL Security Advisory: CVE-2026-6473
- PostgreSQL 18.4, 17.10, 16.14, 15.18, and 14.23 Release Announcement
- PostgreSQL 18.4 Release Notes
- PostgreSQL 16.14 Release Notes
- PostgreSQL 15.18 Release Notes
- Commit e1c30458a: Overflow safe memory allocation macros
- Commit c4d04cc48: Int16 overflow guards in contrib/intarray and contrib/ltree
- Postgres May 2026 Security Update: 11 CVEs, All Versions



