← THE INDEX  ·  WRITEUP

Incomplete CVE-2025-31480 Remediation: Unqualified parse_ident in SECURITY DEFINER Function

After fixing CVE-2025-31480 by schema-qualifying function calls across aiven_extras, one unqualified parse_ident call was missed in a SECURITY DEFINER body, leaving the same vulnerability class present.

Summary

The aiven_extras.pg_create_publication() SECURITY DEFINER function (owner postgres, all privileges) calls parse_ident() without a pg_catalog. schema prefix. This is the same vulnerability class as CVE-2025-31480 (unqualified function calls in SECURITY DEFINER functions enabling search_path hijacking) and CVE-2023-32305. It was not found during the remediation audit that fixed CVE-2025-31480 in version 1.1.16. All 18 other SECURITY DEFINER functions in aiven_extras 1.1.18 use fully qualified pg_catalog.* calls. parse_ident is the single exception.

Note: CVE-2025-31480 is a variant of the same class, not the CVE assigned to this specific report.

Impact

Immediate exploitation is not possible. PostgreSQL always places pg_catalog at the front of the search path regardless of SET search_path, so parse_ident() resolves to pg_catalog.parse_ident() even without the prefix. The risk is in the incomplete hardening pattern. If PostgreSQL's resolution behavior changes, or if parse_ident is moved or renamed in a future version, this becomes immediately exploitable for superuser privilege escalation via search_path injection. Security reviewers checking whether the CVE-2025-31480 fix was complete would find this omission and question the thoroughness of the audit.

Root cause

During the CVE-2025-31480 remediation, the aiven_extras team added pg_catalog. prefixes to format(), array_length(), left(), current_setting(), current_database(), set_config(), and every other function call in all 19 SECURITY DEFINER functions. The audit did not cover parse_ident() in pg_create_publication().

The source code location is sql/aiven_extras.sql, inside pg_create_publication():

FOREACH l_ident IN ARRAY arg_tables LOOP
    l_parsed_ident = parse_ident(l_ident);  -- unqualified

All surrounding calls in the same function body use pg_catalog.format(...), pg_catalog.array_length(...), etc. The omission is visible by diffing the function source against the pg_catalog. pattern used everywhere else.

Proof of concept

The following confirms the unqualified call exists and shows that all other calls in the same function are qualified.

Disclosure and fix

Reported to Aiven through their bug bounty program. Aiven triaged this as P4 (Low) given the lack of immediate exploitability. The fix is a single-line change in sql/aiven_extras.sql:

- l_parsed_ident = parse_ident(l_ident);
+ l_parsed_ident = pg_catalog.parse_ident(l_ident);

References: CVE-2025-31480 advisory at https://github.com/aiven/aiven-extras/security/advisories/GHSA-33xh-jqgf-6627, CVE-2023-32305 advisory at https://github.com/aiven/aiven-extras/security/advisories/GHSA-7r4w-fw4h-67gp, PostgreSQL search_path security guidance at https://wiki.postgresql.org/wiki/A_Guide_to_CVE-2018-1058.