Skip to content

Commit 62e8bbd

Browse files
Validate identifiers substituted into extension scripts (#308)
* Validate identifiers substituted into extension scripts execute_extension_script() substitutes @extowner@ and @extschema@ into the extension script, filtering each value through quote_identifier(). Certain characters (" $ ' \) cannot be quoted consistently both inside and outside of string literals, so a name containing one of them can produce a script that does not parse the way the extension author intended once the substitution is performed. Reject substitution when the owner or schema name contains any of these characters instead of producing such a script. This matches the identifier handling that PostgreSQL core performs for the same substitutions. Extend pg_tle_injection with coverage for both the owner and schema cases, including that ordinary names continue to work. * Test $ and \ in extension script identifier substitution Extend the @extschema@ substitution coverage in pg_tle_injection to the two remaining quoting-relevant characters ($ and \) so all four of " $ ' \ are exercised. Addresses review feedback on PR #308.
1 parent 5a1131f commit 62e8bbd

3 files changed

Lines changed: 167 additions & 0 deletions

File tree

src/tleextension.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1392,6 +1392,16 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
13921392
char *c_sql = read_extension_script_file(control, filename);
13931393
Datum t_sql;
13941394

1395+
/*
1396+
* We filter each substitution through quote_identifier(). When the
1397+
* arg contains one of the following characters, no one collection of
1398+
* quoting can work inside $$dollar-quoted string literals$$,
1399+
* 'single-quoted string literals', and outside of any literal. To
1400+
* avoid a security snare for extension authors, error on substitution
1401+
* for arguments containing these.
1402+
*/
1403+
const char *quoting_relevant_chars = "\"$'\\";
1404+
13951405
/* We use various functions that want to operate on text datums */
13961406
t_sql = CStringGetTextDatum(c_sql);
13971407

@@ -1421,6 +1431,11 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
14211431
t_sql,
14221432
CStringGetTextDatum("@extowner@"),
14231433
CStringGetTextDatum(qUserName));
1434+
if (strpbrk(userName, quoting_relevant_chars))
1435+
ereport(ERROR,
1436+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
1437+
errmsg("invalid character in extension owner: must not contain any of \"%s\"",
1438+
quoting_relevant_chars)));
14241439
}
14251440

14261441
/*
@@ -1432,13 +1447,19 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
14321447
*/
14331448
if (!control->relocatable)
14341449
{
1450+
Datum old = t_sql;
14351451
const char *qSchemaName = quote_identifier(schemaName);
14361452

14371453
t_sql = DirectFunctionCall3Coll(replace_text,
14381454
C_COLLATION_OID,
14391455
t_sql,
14401456
CStringGetTextDatum("@extschema@"),
14411457
CStringGetTextDatum(qSchemaName));
1458+
if (t_sql != old && strpbrk(schemaName, quoting_relevant_chars))
1459+
ereport(ERROR,
1460+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
1461+
errmsg("invalid character in extension \"%s\" schema: must not contain any of \"%s\"",
1462+
control->name, quoting_relevant_chars)));
14421463
}
14431464

14441465
/*

test/expected/pg_tle_injection.out

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,92 @@ $_pg_tle_$
221221
);
222222
ERROR: invalid extension name: "test9.control"(),pg_sleep(10),pgtle."test9"
223223
DETAIL: Extension names must only contain alphanumeric characters or valid separators.
224+
-- @extschema@ and @extowner@ substitutions are filtered through
225+
-- quote_identifier(). A schema or owner name containing a character that
226+
-- cannot be consistently quoted inside and outside of string literals (any of
227+
-- " $ ' \) must be rejected rather than substituted into the script.
228+
-- An extension whose script references @extschema@ cannot be created into a
229+
-- schema whose name contains a quoting-relevant character.
230+
SELECT pgtle.install_extension
231+
(
232+
'ext_schema_subst',
233+
'1.0',
234+
'references @extschema@',
235+
$_pgtle_$
236+
CREATE FUNCTION whereami() RETURNS text AS $$ SELECT '@extschema@' $$ LANGUAGE SQL;
237+
$_pgtle_$
238+
);
239+
install_extension
240+
-------------------
241+
t
242+
(1 row)
243+
244+
CREATE SCHEMA "bad""schema";
245+
-- this should fail
246+
CREATE EXTENSION ext_schema_subst SCHEMA "bad""schema";
247+
ERROR: invalid character in extension "ext_schema_subst" schema: must not contain any of ""$'\"
248+
-- the remaining quoting-relevant characters are rejected as well
249+
CREATE SCHEMA "bad$schema";
250+
-- this should fail
251+
CREATE EXTENSION ext_schema_subst SCHEMA "bad$schema";
252+
ERROR: invalid character in extension "ext_schema_subst" schema: must not contain any of ""$'\"
253+
CREATE SCHEMA "bad\schema";
254+
-- this should fail
255+
CREATE EXTENSION ext_schema_subst SCHEMA "bad\schema";
256+
ERROR: invalid character in extension "ext_schema_subst" schema: must not contain any of ""$'\"
257+
-- a schema with an ordinary name still works
258+
CREATE SCHEMA good_schema;
259+
CREATE EXTENSION ext_schema_subst SCHEMA good_schema;
260+
SELECT good_schema.whereami();
261+
whereami
262+
-------------
263+
good_schema
264+
(1 row)
265+
266+
DROP EXTENSION ext_schema_subst;
267+
SELECT pgtle.uninstall_extension('ext_schema_subst');
268+
uninstall_extension
269+
---------------------
270+
t
271+
(1 row)
272+
273+
DROP SCHEMA "bad""schema";
274+
DROP SCHEMA "bad$schema";
275+
DROP SCHEMA "bad\schema";
276+
DROP SCHEMA good_schema;
277+
-- An extension whose script references @extowner@ cannot be created by a role
278+
-- whose name contains a quoting-relevant character. (The role is created as a
279+
-- superuser only to avoid unrelated privilege setup; the substitution and its
280+
-- validation run regardless of the caller's privileges.)
281+
CREATE ROLE " owner'" SUPERUSER LOGIN;
282+
SELECT pgtle.install_extension
283+
(
284+
'ext_owner_subst',
285+
'1.0',
286+
'references @extowner@',
287+
$_pgtle_$
288+
CREATE FUNCTION owned_by() RETURNS text AS $$ SELECT '@extowner@' $$ LANGUAGE SQL;
289+
$_pgtle_$
290+
);
291+
install_extension
292+
-------------------
293+
t
294+
(1 row)
295+
296+
SET SESSION AUTHORIZATION " owner'";
297+
CREATE SCHEMA owner_schema;
298+
-- this should fail
299+
CREATE EXTENSION ext_owner_subst SCHEMA owner_schema;
300+
ERROR: invalid character in extension owner: must not contain any of ""$'\"
301+
RESET SESSION AUTHORIZATION;
302+
SELECT pgtle.uninstall_extension('ext_owner_subst');
303+
uninstall_extension
304+
---------------------
305+
t
306+
(1 row)
307+
308+
DROP SCHEMA owner_schema;
309+
DROP ROLE " owner'";
224310
-- cleanup
225311
DROP EXTENSION pg_tle;
226312
DROP SCHEMA pgtle;

test/sql/pg_tle_injection.sql

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,66 @@ $_pg_tle_$
175175
$_pg_tle_$
176176
);
177177

178+
-- @extschema@ and @extowner@ substitutions are filtered through
179+
-- quote_identifier(). A schema or owner name containing a character that
180+
-- cannot be consistently quoted inside and outside of string literals (any of
181+
-- " $ ' \) must be rejected rather than substituted into the script.
182+
183+
-- An extension whose script references @extschema@ cannot be created into a
184+
-- schema whose name contains a quoting-relevant character.
185+
SELECT pgtle.install_extension
186+
(
187+
'ext_schema_subst',
188+
'1.0',
189+
'references @extschema@',
190+
$_pgtle_$
191+
CREATE FUNCTION whereami() RETURNS text AS $$ SELECT '@extschema@' $$ LANGUAGE SQL;
192+
$_pgtle_$
193+
);
194+
CREATE SCHEMA "bad""schema";
195+
-- this should fail
196+
CREATE EXTENSION ext_schema_subst SCHEMA "bad""schema";
197+
-- the remaining quoting-relevant characters are rejected as well
198+
CREATE SCHEMA "bad$schema";
199+
-- this should fail
200+
CREATE EXTENSION ext_schema_subst SCHEMA "bad$schema";
201+
CREATE SCHEMA "bad\schema";
202+
-- this should fail
203+
CREATE EXTENSION ext_schema_subst SCHEMA "bad\schema";
204+
-- a schema with an ordinary name still works
205+
CREATE SCHEMA good_schema;
206+
CREATE EXTENSION ext_schema_subst SCHEMA good_schema;
207+
SELECT good_schema.whereami();
208+
DROP EXTENSION ext_schema_subst;
209+
SELECT pgtle.uninstall_extension('ext_schema_subst');
210+
DROP SCHEMA "bad""schema";
211+
DROP SCHEMA "bad$schema";
212+
DROP SCHEMA "bad\schema";
213+
DROP SCHEMA good_schema;
214+
215+
-- An extension whose script references @extowner@ cannot be created by a role
216+
-- whose name contains a quoting-relevant character. (The role is created as a
217+
-- superuser only to avoid unrelated privilege setup; the substitution and its
218+
-- validation run regardless of the caller's privileges.)
219+
CREATE ROLE " owner'" SUPERUSER LOGIN;
220+
SELECT pgtle.install_extension
221+
(
222+
'ext_owner_subst',
223+
'1.0',
224+
'references @extowner@',
225+
$_pgtle_$
226+
CREATE FUNCTION owned_by() RETURNS text AS $$ SELECT '@extowner@' $$ LANGUAGE SQL;
227+
$_pgtle_$
228+
);
229+
SET SESSION AUTHORIZATION " owner'";
230+
CREATE SCHEMA owner_schema;
231+
-- this should fail
232+
CREATE EXTENSION ext_owner_subst SCHEMA owner_schema;
233+
RESET SESSION AUTHORIZATION;
234+
SELECT pgtle.uninstall_extension('ext_owner_subst');
235+
DROP SCHEMA owner_schema;
236+
DROP ROLE " owner'";
237+
178238
-- cleanup
179239
DROP EXTENSION pg_tle;
180240
DROP SCHEMA pgtle;

0 commit comments

Comments
 (0)