diff --git a/src/tleextension.c b/src/tleextension.c index 87f4dae..68c7d33 100644 --- a/src/tleextension.c +++ b/src/tleextension.c @@ -1392,6 +1392,16 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control, char *c_sql = read_extension_script_file(control, filename); Datum t_sql; + /* + * We filter each substitution through quote_identifier(). When the + * arg contains one of the following characters, no one collection of + * quoting can work inside $$dollar-quoted string literals$$, + * 'single-quoted string literals', and outside of any literal. To + * avoid a security snare for extension authors, error on substitution + * for arguments containing these. + */ + const char *quoting_relevant_chars = "\"$'\\"; + /* We use various functions that want to operate on text datums */ t_sql = CStringGetTextDatum(c_sql); @@ -1421,6 +1431,11 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control, t_sql, CStringGetTextDatum("@extowner@"), CStringGetTextDatum(qUserName)); + if (strpbrk(userName, quoting_relevant_chars)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid character in extension owner: must not contain any of \"%s\"", + quoting_relevant_chars))); } /* @@ -1432,6 +1447,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control, */ if (!control->relocatable) { + Datum old = t_sql; const char *qSchemaName = quote_identifier(schemaName); t_sql = DirectFunctionCall3Coll(replace_text, @@ -1439,6 +1455,11 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control, t_sql, CStringGetTextDatum("@extschema@"), CStringGetTextDatum(qSchemaName)); + if (t_sql != old && strpbrk(schemaName, quoting_relevant_chars)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid character in extension \"%s\" schema: must not contain any of \"%s\"", + control->name, quoting_relevant_chars))); } /* diff --git a/test/expected/pg_tle_injection.out b/test/expected/pg_tle_injection.out index 8ab898a..93f9018 100644 --- a/test/expected/pg_tle_injection.out +++ b/test/expected/pg_tle_injection.out @@ -221,6 +221,81 @@ $_pg_tle_$ ); ERROR: invalid extension name: "test9.control"(),pg_sleep(10),pgtle."test9" DETAIL: Extension names must only contain alphanumeric characters or valid separators. +-- @extschema@ and @extowner@ substitutions are filtered through +-- quote_identifier(). A schema or owner name containing a character that +-- cannot be consistently quoted inside and outside of string literals (any of +-- " $ ' \) must be rejected rather than substituted into the script. +-- An extension whose script references @extschema@ cannot be created into a +-- schema whose name contains a quoting-relevant character. +SELECT pgtle.install_extension +( + 'ext_schema_subst', + '1.0', + 'references @extschema@', +$_pgtle_$ + CREATE FUNCTION whereami() RETURNS text AS $$ SELECT '@extschema@' $$ LANGUAGE SQL; +$_pgtle_$ +); + install_extension +------------------- + t +(1 row) + +CREATE SCHEMA "bad""schema"; +-- this should fail +CREATE EXTENSION ext_schema_subst SCHEMA "bad""schema"; +ERROR: invalid character in extension "ext_schema_subst" schema: must not contain any of ""$'\" +-- a schema with an ordinary name still works +CREATE SCHEMA good_schema; +CREATE EXTENSION ext_schema_subst SCHEMA good_schema; +SELECT good_schema.whereami(); + whereami +------------- + good_schema +(1 row) + +DROP EXTENSION ext_schema_subst; +SELECT pgtle.uninstall_extension('ext_schema_subst'); + uninstall_extension +--------------------- + t +(1 row) + +DROP SCHEMA "bad""schema"; +DROP SCHEMA good_schema; +-- An extension whose script references @extowner@ cannot be created by a role +-- whose name contains a quoting-relevant character. (The role is created as a +-- superuser only to avoid unrelated privilege setup; the substitution and its +-- validation run regardless of the caller's privileges.) +CREATE ROLE " owner'" SUPERUSER LOGIN; +SELECT pgtle.install_extension +( + 'ext_owner_subst', + '1.0', + 'references @extowner@', +$_pgtle_$ + CREATE FUNCTION owned_by() RETURNS text AS $$ SELECT '@extowner@' $$ LANGUAGE SQL; +$_pgtle_$ +); + install_extension +------------------- + t +(1 row) + +SET SESSION AUTHORIZATION " owner'"; +CREATE SCHEMA owner_schema; +-- this should fail +CREATE EXTENSION ext_owner_subst SCHEMA owner_schema; +ERROR: invalid character in extension owner: must not contain any of ""$'\" +RESET SESSION AUTHORIZATION; +SELECT pgtle.uninstall_extension('ext_owner_subst'); + uninstall_extension +--------------------- + t +(1 row) + +DROP SCHEMA owner_schema; +DROP ROLE " owner'"; -- cleanup DROP EXTENSION pg_tle; DROP SCHEMA pgtle; diff --git a/test/sql/pg_tle_injection.sql b/test/sql/pg_tle_injection.sql index 0b244d7..c61f940 100644 --- a/test/sql/pg_tle_injection.sql +++ b/test/sql/pg_tle_injection.sql @@ -175,6 +175,57 @@ $_pg_tle_$ $_pg_tle_$ ); +-- @extschema@ and @extowner@ substitutions are filtered through +-- quote_identifier(). A schema or owner name containing a character that +-- cannot be consistently quoted inside and outside of string literals (any of +-- " $ ' \) must be rejected rather than substituted into the script. + +-- An extension whose script references @extschema@ cannot be created into a +-- schema whose name contains a quoting-relevant character. +SELECT pgtle.install_extension +( + 'ext_schema_subst', + '1.0', + 'references @extschema@', +$_pgtle_$ + CREATE FUNCTION whereami() RETURNS text AS $$ SELECT '@extschema@' $$ LANGUAGE SQL; +$_pgtle_$ +); +CREATE SCHEMA "bad""schema"; +-- this should fail +CREATE EXTENSION ext_schema_subst SCHEMA "bad""schema"; +-- a schema with an ordinary name still works +CREATE SCHEMA good_schema; +CREATE EXTENSION ext_schema_subst SCHEMA good_schema; +SELECT good_schema.whereami(); +DROP EXTENSION ext_schema_subst; +SELECT pgtle.uninstall_extension('ext_schema_subst'); +DROP SCHEMA "bad""schema"; +DROP SCHEMA good_schema; + +-- An extension whose script references @extowner@ cannot be created by a role +-- whose name contains a quoting-relevant character. (The role is created as a +-- superuser only to avoid unrelated privilege setup; the substitution and its +-- validation run regardless of the caller's privileges.) +CREATE ROLE " owner'" SUPERUSER LOGIN; +SELECT pgtle.install_extension +( + 'ext_owner_subst', + '1.0', + 'references @extowner@', +$_pgtle_$ + CREATE FUNCTION owned_by() RETURNS text AS $$ SELECT '@extowner@' $$ LANGUAGE SQL; +$_pgtle_$ +); +SET SESSION AUTHORIZATION " owner'"; +CREATE SCHEMA owner_schema; +-- this should fail +CREATE EXTENSION ext_owner_subst SCHEMA owner_schema; +RESET SESSION AUTHORIZATION; +SELECT pgtle.uninstall_extension('ext_owner_subst'); +DROP SCHEMA owner_schema; +DROP ROLE " owner'"; + -- cleanup DROP EXTENSION pg_tle; DROP SCHEMA pgtle;