← THE INDEX  ·  WRITEUP

Autovacuum Arbitrary Code Execution via Expression Index Shadow Functions

Any avnadmin user can execute code in the PostgreSQL superuser context by chaining a gatekeeper bypass with expression index evaluation in the autovacuum background worker.

Summary

Any authenticated avnadmin user on Aiven for PostgreSQL can execute arbitrary PL/pgSQL code in the context of the autovacuum background worker, where session_user is postgres (the platform superuser). The attack chains three issues: a bypass of aiven_gatekeeper's shadow-function protection (see the companion report), expression index evaluation in the autovacuum worker context, and a SECURITY DEFINER dblink chain that is not subject to SECURITY_RESTRICTED_OPERATION enforcement. No user interaction is required. Autovacuum runs automatically once enough rows are inserted or updated.

Impact

The autovacuum background worker runs with session_user=postgres. Customer trigger code that reaches this context passes PostgreSQL's superuser permission checks for operations such as GRANT, COPY TO FILE, and CREATE EXTENSION before being stopped only by the secondary SECURITY_RESTRICTED_OPERATION flag. The SECURITY DEFINER pg_alter_subscription_refresh_publication() call succeeds from the autovacuum context, establishing an unrestricted superuser dblink connection to localhost that is NOT subject to SECURITY_RESTRICTED_OPERATION. Any future injectable parameter in that chain would yield fully unrestricted superuser SQL execution. The expression index and the shadow function persist across restarts, so autovacuum silently calls the payload on every ANALYZE cycle. Log entries from autovacuum do not show the attacker's username.

Root cause

Three independent issues combine:

  1. aiven_gatekeeper bypass. The gatekeeper blocks shadow functions with identical argument types but not those with different, implicitly castable types. Creating public.sha256(text) is allowed even though it shadows pg_catalog.sha256(bytea) at the call site (see companion report).

  2. Expression index evaluation in autovacuum. PostgreSQL ANALYZE evaluates expression index functions to compute column statistics, and it does so inside the autovacuum worker process with session_user=postgres. This is not a new PostgreSQL behavior, but aiven_gatekeeper was not hardened against it.

  3. SECDEF dblink chain not restricted. aiven_extras.pg_alter_subscription_refresh_publication() is SECURITY DEFINER owned by postgres. It constructs a localhost connection string using current_user, which resolves to postgres inside the SECDEF context, and connects via UNIX socket with trust authentication. Because current_user is a superuser, dblink's superuser check passes and a full superuser database session is opened. This new connection is not subject to SECURITY_RESTRICTED_OPERATION.

Proof of concept

The payload below creates a shadow function, baits autovacuum with a large table and an expression index, then swaps the function body to the actual payload. All credentials and instance identifiers have been replaced with placeholders.

Log evidence

When autovacuum fires, the server logs show the background worker (no user/db fields) calling the shadow function:

pid=62303,user=,db=,app=,client=
  WARNING: SECDEF_AV: OK cu=avnadmin su=postgres

The empty user= and db= fields confirm this is a background worker context, not a normal session. The su=postgres confirms the superuser identity. The SECDEF dblink call returns without error in this context, confirming the superuser connection was established.

Disclosure and fix

Reported to Aiven through their bug bounty program. Aiven triaged this as P2 (High). The recommended fixes are:

  1. Extend aiven_gatekeeper to block shadow functions with implicitly castable argument types (see companion gatekeeper bypass report).
  2. Set SECURITY_RESTRICTED_OPERATION for expression index evaluation during autovacuum ANALYZE, matching the behavior already applied to logical replication apply workers.
  3. Have pg_alter_subscription_refresh_publication() connect as the calling user rather than as the SECDEF owner, eliminating the passwordless superuser dblink session.
-- Cleanup
DROP INDEX idx_bait;
DROP TABLE bait;
DROP FUNCTION public.sha256(text);