Pin `search_path` on operator-issued connections
Committed by armru
fix(security): pin `search_path` on operator-issued connections (#10774)
A database owner could plant overloaded built-in operators (`=`, `>`,
...) in the `public` schema and `ALTER DATABASE` / `ALTER ROLE` the
`search_path` so that operator introspection probes, running as the
cluster superuser, resolved those overloads before `pg_catalog`,
yielding superuser access and in-pod RCE via `COPY ... FROM PROGRAM`
(`CWE-426`, same class as `CVE-2018-1058`).
Pin `search_path = pg_catalog, public, pg_temp` as a `pgx`
`RuntimeParam` on every pooled operator connection so it ships in the
`StartupMessage` and takes precedence over tenant-controlled
database/role defaults: `pg_catalog` first defeats any planted shadow,
`public` stays writable for `CREATE EXTENSION`, and `pg_temp` is
searched last. The `pgbouncer` profile opts out, as its admin console
rejects unknown startup parameters.
Close the remaining bypasses and user-facing paths:
- Route the three direct `sql.Open("pgx", ...)` callsites
(`WaitForPrimaryAvailable`, the two WAL-archive helpers) through
`pool.NewDBConnection` so they inherit the pin.
- Bracket user-authored scripts (`postInit*`, logical-import
`PostImportApplicationSQL`) and `CREATE EXTENSION` with a dedicated
connection that `SET`s `"$user", public` and `RESET`s back to the pinned
default,
so they keep standard resolution without leaking it to the pool.
- Pin `search_path = pg_catalog, pg_temp` on the `public.user_search`
`SECURITY DEFINER` function (reached by `pgbouncer` via `libpq`, outside
the pool) and require `proconfig IS NOT NULL` so existing clusters are
upgraded.
- Replace the monitoring transaction's `set_config` workaround with an
explicit `SET LOCAL search_path = pg_catalog, public, pg_temp`.
Add an e2e regression test that plants a shadow `=` operator and flips
the database `search_path`, plus unit tests.
Updates the documentation accordingly.
Signed-off-by: Armando Ruocco <[email protected]>
Signed-off-by: Gabriele Bartolini <[email protected]>
Signed-off-by: Marco Nenciarini <[email protected]>
Co-authored-by: Mehmet Ince <[email protected]>
Co-authored-by: Gabriele Bartolini <[email protected]>
Co-authored-by: Marco Nenciarini <[email protected]>Editor assessment
This commit patches CVE-2026-55769. Few commits explain a latent attack this clearly: the body walks through how a tenant with database ownership can alter search_path so that superuser introspection resolves shadow operators from the public schema. It defends the fix rather than merely describing it, stating why pg_catalog must come first, why public stays in the path for CREATE EXTENSION and why pg_temp is searched last. The follow-up bullets cover direct sql.Open callsites, user-authored scripts bracketed on a dedicated connection, and the SECURITY DEFINER function reachable outside the pool. Tests and docs are mentioned, and the short header needs no breaking-change marker. Contributors and reviewers are attributed correctly.