CVE-2023-32305: Improper Input Validation (CWE-20)
value: aiven-extras is a PostgreSQL extension. Versions prior to 1.1.9 contain a privilege escalation vulnerability, allowing elevation to superuser inside PostgreSQL databases that use the aiven-extras package. The vulnerability leverages missing schema qualifiers on privileged functions called by the aiven-extras extension. A low privileged user can create objects that collide with existing function names, which will then be executed instead. Exploiting this vulnerability could allow a low privileged user to acquire `superuser` privileges, which would allow full, unrestricted access to all data and database functions. And could lead to arbitrary code execution or data access on the underlying host as the `postgres` user. The issue has been patched as of version 1.1.9.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 8.8
Published: 2023-05-12T19:15Z
Repository: https://github.com/aiven/aiven-extras
Language: SQL
Files changed: sql/aiven_extras.sql
Commit date: 2023-05-08 15:59:38 -0400
Commit message: Qualify function calls with pg_catalog schema

The security definer functions `aiven_extras.pg_create_publication` and
`aiven_extras.set_pgaudit_parameter` are missing explicit `pg_catalog`
schema qualifications for array and string function calls such as
`array_length`, `format`, and `left`.

Co-authored-by: Etienne Stalmans <etienne.stalmans@aiven.io>
Co-authored-by: Kevin Michel <kevin.michel@aiven.io>
Co-authored-by: Thomas Krennwallner <teakay@aiven.io>

Diff: diff --git a/sql/aiven_extras.sql b/sql/aiven_extras.sql
index d6a0fc1..f1e4a5e 100644
--- a/sql/aiven_extras.sql
+++ b/sql/aiven_extras.sql
@@ -424,34 +424,34 @@ DECLARE
   l_table_count INT;
   l_tables_command TEXT;
   l_parsed_ident TEXT[];
   l_parsed_arg_tables TEXT[];
 BEGIN
-    l_table_count = array_length(arg_tables, 1);
+    l_table_count = pg_catalog.array_length(arg_tables, 1);
     IF l_table_count >= 1
     THEN
         l_parsed_arg_tables = ARRAY[]::TEXT[];
         l_tables_command = 'CREATE PUBLICATION %I FOR TABLE ';
         FOREACH l_ident IN ARRAY arg_tables LOOP
             l_parsed_ident = parse_ident(l_ident);
-            ASSERT array_length(l_parsed_ident, 1) <= 2, 'Only simple table names or tables qualified with schema names allowed';
+            ASSERT pg_catalog.array_length(l_parsed_ident, 1) <= 2, 'Only simple table names or tables qualified with schema names allowed';
             -- Make sure we pass in a simple list of identifiers, so separate the tables from parent schemas
-            IF array_length(l_parsed_ident, 1) = 2
+            IF pg_catalog.array_length(l_parsed_ident, 1) = 2
             THEN
                 l_tables_command = l_tables_command || '%I.%I, ';
             ELSE
                 l_tables_command = l_tables_command || '%I, ';
             END IF;
             l_parsed_arg_tables = l_parsed_arg_tables || l_parsed_ident;
         END LOOP;
         -- Remove trailing comma and whitespace, add the rest
-        l_tables_command = left(l_tables_command, -2) || ' WITH (publish = %I)';
-        EXECUTE format(l_tables_command, VARIADIC array[arg_publication_name] || l_parsed_arg_tables || arg_publish);
+        l_tables_command = pg_catalog.left(l_tables_command, -2) || ' WITH (publish = %I)';
+        EXECUTE pg_catalog.format(l_tables_command, VARIADIC array[arg_publication_name] || l_parsed_arg_tables || arg_publish);
     ELSE
-        EXECUTE format('CREATE PUBLICATION %I WITH (publish = %I)', arg_publication_name, arg_publish);
+        EXECUTE pg_catalog.format('CREATE PUBLICATION %I WITH (publish = %I)', arg_publication_name, arg_publish);
     END IF;
-    EXECUTE format('ALTER PUBLICATION %I OWNER TO %I', arg_publication_name, session_user);
+    EXECUTE pg_catalog.format('ALTER PUBLICATION %I OWNER TO %I', arg_publication_name, session_user);
 END;
 $$;
 
 
 DROP FUNCTION IF EXISTS aiven_extras.set_pgaudit_parameter(TEXT, TEXT, TEXT);
@@ -487,11 +487,11 @@ BEGIN
         'log_statement_once'
     ) THEN
         RAISE EXCEPTION 'Invalid parameter: %', arg_parameter;
     END IF;
 
-    EXECUTE format('ALTER DATABASE %I SET pgaudit.%I = %L',
+    EXECUTE pg_catalog.format('ALTER DATABASE %I SET pgaudit.%I = %L',
         arg_database,
         arg_parameter,
         arg_value
     );
 END;

Vulnerable code:     l_table_count = array_length(arg_tables, 1);
            ASSERT array_length(l_parsed_ident, 1) <= 2, 'Only simple table names or tables qualified with schema names allowed';
            IF array_length(l_parsed_ident, 1) = 2
        l_tables_command = left(l_tables_command, -2) || ' WITH (publish = %I)';
        EXECUTE format(l_tables_command, VARIADIC array[arg_publication_name] || l_parsed_arg_tables || arg_publish);
        EXECUTE format('CREATE PUBLICATION %I WITH (publish = %I)', arg_publication_name, arg_publish);
    EXECUTE format('ALTER PUBLICATION %I OWNER TO %I', arg_publication_name, session_user);
    EXECUTE format('ALTER DATABASE %I SET pgaudit.%I = %L',
Fixed code:     l_table_count = pg_catalog.array_length(arg_tables, 1);
            ASSERT pg_catalog.array_length(l_parsed_ident, 1) <= 2, 'Only simple table names or tables qualified with schema names allowed';
            IF pg_catalog.array_length(l_parsed_ident, 1) = 2
        l_tables_command = pg_catalog.left(l_tables_command, -2) || ' WITH (publish = %I)';
        EXECUTE pg_catalog.format(l_tables_command, VARIADIC array[arg_publication_name] || l_parsed_arg_tables || arg_publish);
        EXECUTE pg_catalog.format('CREATE PUBLICATION %I WITH (publish = %I)', arg_publication_name, arg_publish);
    EXECUTE pg_catalog.format('ALTER PUBLICATION %I OWNER TO %I', arg_publication_name, session_user);
    EXECUTE pg_catalog.format('ALTER DATABASE %I SET pgaudit.%I = %L',