52 CVEs fixed, but apt still serves the old build
PostgreSQL shipped fixes for 52 CVEs on November 12, including CVE-2026-6460, a type-confusion bug that lets an unprivileged database user crash the server and, with more work, run code as the OS user behind it. On the morning of November 13, Ubuntu's apt still served PostgreSQL 16.15 — one minor behind the fix. I installed it, ran the public crash proof-of-concept as a login role with no privileges, and watched the backend die with signal 11.
The fix exists. Your package manager may not have it
The November 12 release covers every supported branch at once: 18.7, 17.12, 16.16, 15.20, and 14.25 — the last of those being PostgreSQL 14's final release ever. CVE-2026-6460 is the headline: CVSS 8.8, vector AV:N/AC:L/PR:L/UI:N, reachable over the network with low privileges and no user interaction. The fix adds composite-type attribute versioning, so a prepared value can no longer be reinterpreted after its type changes.
Here is the patch gap I measured this morning, November 13, around 07:00 PT:
channel fixed build available? measured how Docker Hub yes (18.7, 17.12, 16.16) tag API returns 200 Ubuntu 24.04 apt no (16.15 is candidate) apt-cache policy, this machine Docker Hub has the fixed tags. Ubuntu's archive does not — postgresql-16 candidate is still 16.15-0ubuntu0.24.04.1. RDS, per reporting yesterday, still listed 18.6 as newest. The upstream release is 19 hours old and the three distribution channels are already on three different timelines. That window is the story: the CVE is public, the proof-of-concept is public, and the fixed build is one apt-get upgrade away from existing — except the upgrade does not exist yet.
The crash, reproduced on the build apt serves
The bug is a 22-year-old type confusion, documented by researcher Mehmet Ince alongside other reporters credited in the fix. The primitive dates to a commit from May 5, 2004: prepare a query holding a value in a composite type, change one attribute's type with ALTER TYPE, then execute. The prepared plan still holds the old bytes; the output function reads them under the new type. I ran the published crash case against the apt-installed 16.15, as a role with no privileges beyond LOGIN:
CREATE TYPE pg_temp.poc AS (key uuid); PREPARE pc_crash AS SELECT '(01020000-0000-0000-0000-000000000000)' ::pg_temp.poc; ALTER TYPE pg_temp.poc ALTER ATTRIBUTE key TYPE bytea CASCADE; EXECUTE pc_crash; psql: SSL SYSCALL error: EOF detected psql: error: connection to server was lost The server log says what happened, verbatim:
LOG: server process (PID 12077) was terminated by signal 11: Segmentation fault DETAIL: Failed process was running: EXECUTE pc_crash; LOG: terminating any other active server processes LOG: all server processes terminated; reinitializing Two details worth pausing on. First, the trigger is six SQL statements and zero privileges — creating a temporary type and preparing a statement needs no superuser. Second, the crash is not contained to my session: the postmaster killed every other backend and reinitialized the whole server. One unprivileged connection turned into a full restart cycle.
Why those particular bytes crash it: the UUID 01020000-... starts with 01 02, which byteaout() reads as an external expanded-object header, and the following eight zero bytes become a NULL object pointer. The detoasting path then dereferences it. The researcher who found this went further — the same confusion can forge a fake expanded object whose methods table points at system(), and a second confusion leaks real heap addresses to defeat ASLR. I stopped at the crash and the leak, deliberately: the crash alone proves the exposure, and I am not going to hand out a working RCE recipe.
The leak, also reproduced
The address-leak variant uses the same prepare-mutate-execute shape, but with a three-UUID composite type forged into a 48-byte array carrier, so unnest() returns a pointer that SQL prints as a bigint. Three executions, same session, as the same unprivileged role:
to_hex -------------- 64f668a6142c 64f668a63b0c 64f668a63b0c Real heap pointers, printed by a SELECT, to a user who cannot read a single row they were not granted. The second and third runs return the same address — the allocation is reused across executions in the session, which is exactly what makes the leak usable rather than trivia.
What to do about it
If you run PostgreSQL from Docker, the fixed images are already there — postgres:18.7, 17.12, 16.16 all resolve. If you run it from Ubuntu's archive, there is nothing to upgrade to yet: check SELECT version(); and apt-cache policy postgresql-16, and do not assume a November 12 upstream release means a November 13 patched system. Managed users are on their provider's schedule — check yours rather than the announcement date.
The uncomfortable part is the arithmetic of the window. The bug is 22 years old, the fix is a day old, the exploit write-up is public, and the most common way people install Postgres on a server still hands out the vulnerable build. The patch exists. The distribution lag is the vulnerability now.
Sources: PostgreSQL November 12, 2026 security release (18.7, 17.12, 16.16, 15.20, 14.25; 52 CVEs); Mehmet Ince, "Turning a 22-Year-Old PostgreSQL Type-Confusion Primitive into RCE (CVE-2026-6460)"; CVE-2026-6460 advisory (CVSS 8.8, AV:N/AC:L/PR:L/UI:N). The crash, the leak, the server log lines, the apt policy output, and the Docker Hub tag checks are my own runs on PostgreSQL 16.15 (Ubuntu 16.15-0ubuntu0.24.04.1), November 13, 2026. I did not build or run the full RCE chain.


