NEWS

22-year-old PostgreSQL bug becomes remote code execution flaw

22-year-old PostgreSQL bug becomes remote code execution flaw
Image: Redação iMasters
News
Reading
5 min
Code
1
In short

Researcher Mehmet Ince published, on November 9, 2026, the third part of a series describing how he turned an old type confusion in the PostgreSQL core into CVE-2026-6460, with potential for remote code execution.

A 22-year-old flaw, a recent CVE

On November 9, 2026, security researcher Mehmet Ince published the third part of a six-post series called "Systemic Risks in the Managed PostgreSQL Industry." In it, he narrates how he arrived at CVE-2026-6460: a type confusion vulnerability that, according to him, has existed in the PostgreSQL core for 22 years.

The finding grew out of earlier work. Between June and July 2026, Ince was exploring memory corruption vulnerabilities in PostgreSQL extensions, among them pg_clickhouse, which translates ClickHouse values into PostgreSQL's native format. He reported the pg_clickhouse flaw to the vendor, who fixed the issue on June 23, 2026. But the process of exploiting that extension left one question open: how does PostgreSQL internally decide what each value is?

From Datum to type confusion, with an AI agent as teacher

To answer that question, Ince went straight to the database's internal code. He says he used an AI agent, which he named pg_expert (running on the Opus 5 model), as a tutor to understand concepts such as Datum and OID (Object Identifier).

The logic he describes is easy to summarize: every value inside PostgreSQL travels as a Datum, a generic 8-byte container that carries no information about its own type on its own. What says what that Datum represents is a separate OID, which points to a row in the pg_type catalog: it is that row that defines which input (typinput) and output (typoutput) function Postgres will call to interpret the bytes.

This led Ince to a central question, which he records in these terms:

What if I could somehow change the type between the moment PostgreSQL constructed a value and the moment it used that value?

Mehmet Ince, security researcher

The trick: changing the type after the query has already been parsed

The technical answer involves three PostgreSQL features combined in a way that, according to Ince himself, surprises even those who know the database's internals well: composite types, PREPARE/EXECUTE, and ALTER TYPE ... ALTER ATTRIBUTE.

The step-by-step he demonstrates is this:

sql
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;

The PREPARE makes PostgreSQL parse and plan the query once, treating the key attribute as uuid. Before the EXECUTE, though, Ince changes the composite type's definition so that the same attribute becomes bytea. When EXECUTE runs, the database interprets the bytes that were a valid UUID as if they were the internal representation of a bytea, and it crashes with a segmentation fault.

The difference compared to an earlier technique of Ince's own (abusing LANGUAGE internal to force native functions to read a Datum as if it were a different type) is the level of privilege required. That one required superuser access; this one, according to him, can be triggered by any login role with the lowest possible privilege.

Why this is more serious than a crash

A segmentation fault is already serious in production, but the angle that gives the CVE its name is remote code execution. The piece that explains this is the bytea format.

Internally, bytea uses a representation called varlena, with a 4-byte header before the content. Ince describes two possible formats for that header:

  • Inline representation: the initial bytes only describe the size of the value that follows.
  • Expanded external representation: when the header starts with 01, PostgreSQL understands that what follows is not the data itself, but a pointer to an object in memory.

In the UUID example used as proof of concept, the 16 bytes controlled by the attacker start precisely with 01 02, exactly the pattern that, if the field were indeed a bytea, would make Postgres treat the rest of the bytes as a memory address. Ince states that this is "the most reliable exploitation primitive" he has ever seen in PostgreSQL vulnerabilities, because it gives an attacker the chance to control an internal pointer from data that was, nominally, just a UUID.

This third part of the series ends with the discovery of the crash and the analysis of why it happens; the full path to code execution, according to the post's own numbering (3 of 6), should be detailed in the following parts of the series.

What this means for those running PostgreSQL in Brazil

The most practical point of the report is the level of privilege required. Creating a temporary composite type, using PREPARE/EXECUTE, and running ALTER TYPE on a type you created yourself does not require superuser access, just a regular authenticated connection.

This matters especially for three scenarios common among Brazilian teams:

  • Managed databases: Ince's series is called "Systemic Risks in the Managed PostgreSQL Industry," and he says he believes the problem affects "probably all Postgres-compatible offerings," a category that includes services such as RDS, Cloud SQL, Supabase, Neon, and Crunchy Data, widely used by startups and companies in Brazil.
  • Multi-tenant environments, where each customer or team runs with a low-privilege role inside the same instance: the report suggests that privilege isolation alone does not block the attack.
  • Applications that expose SQL in some form (BI dashboards, internal notebooks, data tools, endpoints that accept custom queries): any surface that allows running CREATE TYPE, PREPARE, and ALTER TYPE in sequence becomes a potential path.

As of this report's publication, the available material does not include an official PostgreSQL advisory with affected versions and fix status for CVE-2026-6460; that confirmation, if it already exists, should come from the PostgreSQL project's official security channel or from the next parts of Ince's series. It is worth following the researcher's blog and your managed database provider's security changelog in the coming days.

Translated from the Brazilian Portuguese original · Read the original