Skip to content

Commit cd5f2a3

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 2bdd7b2 commit cd5f2a3

File tree

7 files changed

+79
-15
lines changed

7 files changed

+79
-15
lines changed

src/backend/commands/extension.c

+27
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,16 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
996996
char *c_sql = read_extension_script_file(control, filename);
997997
Datum t_sql;
998998

999+
/*
1000+
* We filter each substitution through quote_identifier(). When the
1001+
* arg contains one of the following characters, no one collection of
1002+
* quoting can work inside $$dollar-quoted string literals$$,
1003+
* 'single-quoted string literals', and outside of any literal. To
1004+
* avoid a security snare for extension authors, error on substitution
1005+
* for arguments containing these.
1006+
*/
1007+
const char *quoting_relevant_chars = "\"$'\\";
1008+
9991009
/* We use various functions that want to operate on text datums */
10001010
t_sql = CStringGetTextDatum(c_sql);
10011011

@@ -1025,6 +1035,11 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
10251035
t_sql,
10261036
CStringGetTextDatum("@extowner@"),
10271037
CStringGetTextDatum(qUserName));
1038+
if (strpbrk(userName, quoting_relevant_chars))
1039+
ereport(ERROR,
1040+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
1041+
errmsg("invalid character in extension owner: must not contain any of \"%s\"",
1042+
quoting_relevant_chars)));
10281043
}
10291044

10301045
/*
@@ -1036,13 +1051,19 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
10361051
*/
10371052
if (!control->relocatable)
10381053
{
1054+
Datum old = t_sql;
10391055
const char *qSchemaName = quote_identifier(schemaName);
10401056

10411057
t_sql = DirectFunctionCall3Coll(replace_text,
10421058
C_COLLATION_OID,
10431059
t_sql,
10441060
CStringGetTextDatum("@extschema@"),
10451061
CStringGetTextDatum(qSchemaName));
1062+
if (t_sql != old && strpbrk(schemaName, quoting_relevant_chars))
1063+
ereport(ERROR,
1064+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
1065+
errmsg("invalid character in extension \"%s\" schema: must not contain any of \"%s\"",
1066+
control->name, quoting_relevant_chars)));
10461067
}
10471068

10481069
/*
@@ -1052,6 +1073,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
10521073
Assert(list_length(control->requires) == list_length(requiredSchemas));
10531074
forboth(lc, control->requires, lc2, requiredSchemas)
10541075
{
1076+
Datum old = t_sql;
10551077
char *reqextname = (char *) lfirst(lc);
10561078
Oid reqschema = lfirst_oid(lc2);
10571079
char *schemaName = get_namespace_name(reqschema);
@@ -1064,6 +1086,11 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
10641086
t_sql,
10651087
CStringGetTextDatum(repltoken),
10661088
CStringGetTextDatum(qSchemaName));
1089+
if (t_sql != old && strpbrk(schemaName, quoting_relevant_chars))
1090+
ereport(ERROR,
1091+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
1092+
errmsg("invalid character in extension \"%s\" schema: must not contain any of \"%s\"",
1093+
reqextname, quoting_relevant_chars)));
10671094
}
10681095

10691096
/*

src/test/modules/test_extensions/Makefile

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ PGFILEDESC = "test_extensions - regression testing for EXTENSION support"
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 \
88
test_ext_cyclic1 test_ext_cyclic2 \
9+
test_ext_extschema \
910
test_ext_evttrig \
1011
test_ext_req_schema1 test_ext_req_schema2 test_ext_req_schema3
1112

@@ -15,6 +16,7 @@ DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
1516
test_ext_cine--1.0.sql test_ext_cine--1.0--1.1.sql \
1617
test_ext_cor--1.0.sql \
1718
test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql \
19+
test_ext_extschema--1.0.sql \
1820
test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql \
1921
test_ext_req_schema1--1.0.sql \
2022
test_ext_req_schema2--1.0.sql \

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

+24-11
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;
@@ -312,6 +313,13 @@ Objects in extension "test_ext_cine"
312313
table ext_cine_tab3
313314
(9 rows)
314315

316+
--
317+
-- Test @extschema@ syntax.
318+
--
319+
CREATE SCHEMA "has space";
320+
CREATE EXTENSION test_ext_extschema SCHEMA has$dollar;
321+
ERROR: invalid character in extension "test_ext_extschema" schema: must not contain any of ""$'\"
322+
CREATE EXTENSION test_ext_extschema SCHEMA "has space";
315323
--
316324
-- Test extension with objects outside the extension's schema.
317325
--
@@ -358,6 +366,11 @@ DROP SCHEMA test_func_dep3;
358366
--
359367
-- Test @extschema:extname@ syntax and no_relocate option
360368
--
369+
CREATE EXTENSION test_ext_req_schema1 SCHEMA has$dollar;
370+
CREATE EXTENSION test_ext_req_schema3 CASCADE;
371+
NOTICE: installing required extension "test_ext_req_schema2"
372+
ERROR: invalid character in extension "test_ext_req_schema1" schema: must not contain any of ""$'\"
373+
DROP EXTENSION test_ext_req_schema1;
361374
CREATE SCHEMA test_s_dep;
362375
CREATE EXTENSION test_ext_req_schema1 SCHEMA test_s_dep;
363376
CREATE EXTENSION test_ext_req_schema3 CASCADE;

src/test/modules/test_extensions/meson.build

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ test_install_data += files(
2727
'test_ext_cyclic1.control',
2828
'test_ext_cyclic2--1.0.sql',
2929
'test_ext_cyclic2.control',
30+
'test_ext_extschema--1.0.sql',
31+
'test_ext_extschema.control',
3032
'test_ext_evttrig--1.0--2.0.sql',
3133
'test_ext_evttrig--1.0.sql',
3234
'test_ext_evttrig.control',

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

+16-4
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;
@@ -210,6 +212,13 @@ ALTER EXTENSION test_ext_cine UPDATE TO '1.1';
210212

211213
\dx+ test_ext_cine
212214

215+
--
216+
-- Test @extschema@ syntax.
217+
--
218+
CREATE SCHEMA "has space";
219+
CREATE EXTENSION test_ext_extschema SCHEMA has$dollar;
220+
CREATE EXTENSION test_ext_extschema SCHEMA "has space";
221+
213222
--
214223
-- Test extension with objects outside the extension's schema.
215224
--
@@ -245,6 +254,9 @@ DROP SCHEMA test_func_dep3;
245254
--
246255
-- Test @extschema:extname@ syntax and no_relocate option
247256
--
257+
CREATE EXTENSION test_ext_req_schema1 SCHEMA has$dollar;
258+
CREATE EXTENSION test_ext_req_schema3 CASCADE;
259+
DROP EXTENSION test_ext_req_schema1;
248260
CREATE SCHEMA test_s_dep;
249261
CREATE EXTENSION test_ext_req_schema1 SCHEMA test_s_dep;
250262
CREATE EXTENSION test_ext_req_schema3 CASCADE;
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@;
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)