Skip to content

Commit eb044d8

Browse files
committed
Reject substituting extension schemas or owners matching ["$'\].
Substituting such values in extension scripts facilitated SQL injection when @extowner@, @extschema@, or @extschema:...@ appeared inside a quoting construct (dollar quoting, '', or ""). No bundled extension was vulnerable. Vulnerable uses do appear in a documentation example and in non-bundled extensions. Hence, the attack prerequisite was an administrator having installed files of a vulnerable, trusted, non-bundled extension. Subject to that prerequisite, this enabled an attacker having database-level CREATE privilege to execute arbitrary code as the bootstrap superuser. By blocking this attack in the core server, there's no need to modify individual extensions. Back-patch to v11 (all supported versions). Reported by Micah Gate, Valerie Woolard, Tim Carey-Smith, and Christoph Berg. Security: CVE-2023-39417
1 parent 0015612 commit eb044d8

File tree

6 files changed

+60
-17
lines changed

6 files changed

+60
-17
lines changed

src/backend/commands/extension.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,16 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
888888
char *c_sql = read_extension_script_file(control, filename);
889889
Datum t_sql;
890890

891+
/*
892+
* We filter each substitution through quote_identifier(). When the
893+
* arg contains one of the following characters, no one collection of
894+
* quoting can work inside $$dollar-quoted string literals$$,
895+
* 'single-quoted string literals', and outside of any literal. To
896+
* avoid a security snare for extension authors, error on substitution
897+
* for arguments containing these.
898+
*/
899+
const char *quoting_relevant_chars = "\"$'\\";
900+
891901
/* We use various functions that want to operate on text datums */
892902
t_sql = CStringGetTextDatum(c_sql);
893903

@@ -912,13 +922,19 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
912922
*/
913923
if (!control->relocatable)
914924
{
925+
Datum old = t_sql;
915926
const char *qSchemaName = quote_identifier(schemaName);
916927

917928
t_sql = DirectFunctionCall3Coll(replace_text,
918929
C_COLLATION_OID,
919930
t_sql,
920931
CStringGetTextDatum("@extschema@"),
921932
CStringGetTextDatum(qSchemaName));
933+
if (t_sql != old && strpbrk(schemaName, quoting_relevant_chars))
934+
ereport(ERROR,
935+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
936+
errmsg("invalid character in extension \"%s\" schema: must not contain any of \"%s\"",
937+
control->name, quoting_relevant_chars)));
922938
}
923939

924940
/*

src/test/modules/test_extensions/Makefile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ PGFILEDESC = "test_extensions - regression testing for EXTENSION support"
55

66
EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 test_ext6 \
77
test_ext7 test_ext8 test_ext_cine test_ext_cor \
8-
test_ext_cyclic1 test_ext_cyclic2
8+
test_ext_cyclic1 test_ext_cyclic2 \
9+
test_ext_extschema
910
DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
1011
test_ext4--1.0.sql test_ext5--1.0.sql test_ext6--1.0.sql \
1112
test_ext7--1.0.sql test_ext7--1.0--2.0.sql test_ext8--1.0.sql \
1213
test_ext_cine--1.0.sql test_ext_cine--1.0--1.1.sql \
1314
test_ext_cor--1.0.sql \
14-
test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql
15+
test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql \
16+
test_ext_extschema--1.0.sql
1517

1618
REGRESS = test_extensions test_extdepend
1719

src/test/modules/test_extensions/expected/test_extensions.out

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
CREATE SCHEMA has$dollar;
12
-- test some errors
23
CREATE EXTENSION test_ext1;
34
ERROR: required extension "test_ext2" is not installed
@@ -6,35 +7,35 @@ CREATE EXTENSION test_ext1 SCHEMA test_ext1;
67
ERROR: schema "test_ext1" does not exist
78
CREATE EXTENSION test_ext1 SCHEMA test_ext;
89
ERROR: schema "test_ext" does not exist
9-
CREATE SCHEMA test_ext;
10-
CREATE EXTENSION test_ext1 SCHEMA test_ext;
10+
CREATE EXTENSION test_ext1 SCHEMA has$dollar;
1111
ERROR: extension "test_ext1" must be installed in schema "test_ext1"
1212
-- finally success
13-
CREATE EXTENSION test_ext1 SCHEMA test_ext CASCADE;
13+
CREATE EXTENSION test_ext1 SCHEMA has$dollar CASCADE;
1414
NOTICE: installing required extension "test_ext2"
1515
NOTICE: installing required extension "test_ext3"
1616
NOTICE: installing required extension "test_ext5"
1717
NOTICE: installing required extension "test_ext4"
1818
SELECT extname, nspname, extversion, extrelocatable FROM pg_extension e, pg_namespace n WHERE extname LIKE 'test_ext%' AND e.extnamespace = n.oid ORDER BY 1;
19-
extname | nspname | extversion | extrelocatable
20-
-----------+-----------+------------+----------------
21-
test_ext1 | test_ext1 | 1.0 | f
22-
test_ext2 | test_ext | 1.0 | t
23-
test_ext3 | test_ext | 1.0 | t
24-
test_ext4 | test_ext | 1.0 | t
25-
test_ext5 | test_ext | 1.0 | t
19+
extname | nspname | extversion | extrelocatable
20+
-----------+------------+------------+----------------
21+
test_ext1 | test_ext1 | 1.0 | f
22+
test_ext2 | has$dollar | 1.0 | t
23+
test_ext3 | has$dollar | 1.0 | t
24+
test_ext4 | has$dollar | 1.0 | t
25+
test_ext5 | has$dollar | 1.0 | t
2626
(5 rows)
2727

2828
CREATE EXTENSION test_ext_cyclic1 CASCADE;
2929
NOTICE: installing required extension "test_ext_cyclic2"
3030
ERROR: cyclic dependency detected between extensions "test_ext_cyclic1" and "test_ext_cyclic2"
31-
DROP SCHEMA test_ext CASCADE;
31+
DROP SCHEMA has$dollar CASCADE;
3232
NOTICE: drop cascades to 5 other objects
3333
DETAIL: drop cascades to extension test_ext3
3434
drop cascades to extension test_ext5
3535
drop cascades to extension test_ext2
3636
drop cascades to extension test_ext4
3737
drop cascades to extension test_ext1
38+
CREATE SCHEMA has$dollar;
3839
CREATE EXTENSION test_ext6;
3940
DROP EXTENSION test_ext6;
4041
CREATE EXTENSION test_ext6;
@@ -307,3 +308,10 @@ Objects in extension "test_ext_cine"
307308
table ext_cine_tab3
308309
(9 rows)
309310

311+
--
312+
-- Test @extschema@ syntax.
313+
--
314+
CREATE SCHEMA "has space";
315+
CREATE EXTENSION test_ext_extschema SCHEMA has$dollar;
316+
ERROR: invalid character in extension "test_ext_extschema" schema: must not contain any of ""$'\"
317+
CREATE EXTENSION test_ext_extschema SCHEMA "has space";

src/test/modules/test_extensions/sql/test_extensions.sql

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1+
CREATE SCHEMA has$dollar;
2+
13
-- test some errors
24
CREATE EXTENSION test_ext1;
35
CREATE EXTENSION test_ext1 SCHEMA test_ext1;
46
CREATE EXTENSION test_ext1 SCHEMA test_ext;
5-
CREATE SCHEMA test_ext;
6-
CREATE EXTENSION test_ext1 SCHEMA test_ext;
7+
CREATE EXTENSION test_ext1 SCHEMA has$dollar;
78

89
-- finally success
9-
CREATE EXTENSION test_ext1 SCHEMA test_ext CASCADE;
10+
CREATE EXTENSION test_ext1 SCHEMA has$dollar CASCADE;
1011

1112
SELECT extname, nspname, extversion, extrelocatable FROM pg_extension e, pg_namespace n WHERE extname LIKE 'test_ext%' AND e.extnamespace = n.oid ORDER BY 1;
1213

1314
CREATE EXTENSION test_ext_cyclic1 CASCADE;
1415

15-
DROP SCHEMA test_ext CASCADE;
16+
DROP SCHEMA has$dollar CASCADE;
17+
CREATE SCHEMA has$dollar;
1618

1719
CREATE EXTENSION test_ext6;
1820
DROP EXTENSION test_ext6;
@@ -203,3 +205,10 @@ CREATE EXTENSION test_ext_cine;
203205
ALTER EXTENSION test_ext_cine UPDATE TO '1.1';
204206

205207
\dx+ test_ext_cine
208+
209+
--
210+
-- Test @extschema@ syntax.
211+
--
212+
CREATE SCHEMA "has space";
213+
CREATE EXTENSION test_ext_extschema SCHEMA has$dollar;
214+
CREATE EXTENSION test_ext_extschema SCHEMA "has space";
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/* src/test/modules/test_extensions/test_ext_extschema--1.0.sql */
2+
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
3+
\echo Use "CREATE EXTENSION test_ext_extschema" to load this file. \quit
4+
5+
SELECT 1 AS @extschema@;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
comment = 'test @extschema@'
2+
default_version = '1.0'
3+
relocatable = false

0 commit comments

Comments
 (0)