diff --git a/src/tleextension.c b/src/tleextension.c index 0b920c4..91b92d1 100644 --- a/src/tleextension.c +++ b/src/tleextension.c @@ -556,6 +556,25 @@ check_valid_version_name(const char *versionname) errdetail("Version names must not contain directory separator characters."))); } +/* + * pg_tle represents each extension artifact as a function whose name is + * derived from the extension name and version (e.g. ".control" or + * "--.sql"). PostgreSQL silently truncates identifiers at + * NAMEDATALEN, so a derived name that exceeds that limit would collide with + * other artifacts of the same extension once truncated. Reject such names up + * front with a clear message rather than create colliding functions. + */ +static void +check_generated_function_name(const char *funcname) +{ + if (strlen(funcname) >= NAMEDATALEN) + ereport(ERROR, + (errcode(ERRCODE_NAME_TOO_LONG), + errmsg("generated function name \"%s\" is too long", funcname), + errdetail("pg_tle function names must be less than %d bytes.", + NAMEDATALEN))); +} + /* * Utility functions to handle extension-related path names */ @@ -4641,6 +4660,8 @@ pg_tle_install_extension(PG_FUNCTION_ARGS) */ sqlname = psprintf("%s--%s.sql", extname, extvers); ctlname = psprintf("%s.control", extname); + check_generated_function_name(sqlname); + check_generated_function_name(ctlname); /* * Check if PG_TLE_EXTNAME is in the list of requirements. Meanwhile, also @@ -4859,6 +4880,7 @@ pg_tle_install_extension_version_sql(PG_FUNCTION_ARGS) * Build appropriate function names based on extension name and version */ sqlname = psprintf("%s--%s.sql", extname, extvers); + check_generated_function_name(sqlname); /* * Validate that there are no injections using the dollar-quoted strings @@ -5012,6 +5034,7 @@ pg_tle_install_update_path(PG_FUNCTION_ARGS) errhint("This may be an attempt at a SQL injection attack. Please verify your installation file."))); sqlname = psprintf("%s--%s--%s.sql", extname, fromvers, tovers); + check_generated_function_name(sqlname); sqlsql = psprintf( "CREATE FUNCTION %s.%s() RETURNS TEXT AS %s" "SELECT %s%s%s%s LANGUAGE SQL", diff --git a/test/expected/pg_tle_management.out b/test/expected/pg_tle_management.out index a9bb835..b6dc0b3 100644 --- a/test/expected/pg_tle_management.out +++ b/test/expected/pg_tle_management.out @@ -860,6 +860,97 @@ SELECT pgtle.uninstall_extension('foo@bar'); t (1 row) +-- pg_tle represents each extension artifact as a function whose name is +-- derived from the extension name and version (e.g. ".control" or +-- "--.sql"). PostgreSQL truncates identifiers at NAMEDATALEN +-- (63 bytes), so a derived name longer than that would collide with the +-- extension's other artifacts once truncated. These must be rejected up +-- front rather than silently create colliding functions. +-- An extension name that overflows the generated function name is rejected +-- and nothing is created. +SELECT pgtle.install_extension +( + 'test9greaterthanNAMEDATALENNAMEDATALENNAMEDATALENNAMEDATALENNAMEDATALENNAMEDATALEN', + '0.1', + 'comment', + $_pgtle_$ SELECT 1; $_pgtle_$ +); +ERROR: generated function name "test9greaterthanNAMEDATALENNAMEDATALENNAMEDATALENNAMEDATALENNAMEDATALENNAMEDATALEN--0.1.sql" is too long +DETAIL: pg_tle function names must be less than 64 bytes. +SELECT count(*) AS leftover_funcs FROM pg_proc + WHERE pronamespace = 'pgtle'::regnamespace AND proname LIKE 'test9%'; + leftover_funcs +---------------- + 0 +(1 row) + +-- A long version on a short name overflows the "--.sql" form and is +-- likewise rejected, both via install_extension and install_extension_version. +SELECT pgtle.install_extension +( + 'shortname', + '012345678901234567890123456789012345678901234567890123456789', + 'comment', + $_pgtle_$ SELECT 1; $_pgtle_$ +); +ERROR: generated function name "shortname--012345678901234567890123456789012345678901234567890123456789.sql" is too long +DETAIL: pg_tle function names must be less than 64 bytes. +SELECT pgtle.install_extension('shortname', '1.0', 'comment', $_pgtle_$ SELECT 1; $_pgtle_$); + install_extension +------------------- + t +(1 row) + +SELECT pgtle.install_extension_version_sql +( + 'shortname', + '012345678901234567890123456789012345678901234567890123456789', + $_pgtle_$ SELECT 1; $_pgtle_$ +); +ERROR: generated function name "shortname--012345678901234567890123456789012345678901234567890123456789.sql" is too long +DETAIL: pg_tle function names must be less than 64 bytes. +-- ... and the "----.sql" update-path form too. +SELECT pgtle.install_update_path +( + 'shortname', + '1.0', + '012345678901234567890123456789012345678901234567890123456789', + $_pgtle_$ SELECT 1; $_pgtle_$ +); +ERROR: generated function name "shortname--1.0--012345678901234567890123456789012345678901234567890123456789.sql" is too long +DETAIL: pg_tle function names must be less than 64 bytes. +SELECT pgtle.uninstall_extension('shortname'); + uninstall_extension +--------------------- + t +(1 row) + +-- A name at the limit (the generated ".sql" name is 63 bytes) still works. +SELECT pgtle.install_extension +( + 'n5456789012345678901234567890123456789012345678901234', + '1.0', + 'comment', + $_pgtle_$ SELECT 1; $_pgtle_$ +); + install_extension +------------------- + t +(1 row) + +SELECT name FROM pgtle.available_extensions() + WHERE name = 'n5456789012345678901234567890123456789012345678901234'; + name +------------------------------------------------------- + n5456789012345678901234567890123456789012345678901234 +(1 row) + +SELECT pgtle.uninstall_extension('n5456789012345678901234567890123456789012345678901234'); + uninstall_extension +--------------------- + t +(1 row) + -- Skip TransactionStmts BEGIN; SELECT pgtle.available_extension_versions(); diff --git a/test/sql/pg_tle_management.sql b/test/sql/pg_tle_management.sql index ace0901..2de2bcf 100644 --- a/test/sql/pg_tle_management.sql +++ b/test/sql/pg_tle_management.sql @@ -553,6 +553,63 @@ SELECT at_func(); DROP EXTENSION "foo@bar"; SELECT pgtle.uninstall_extension('foo@bar'); +-- pg_tle represents each extension artifact as a function whose name is +-- derived from the extension name and version (e.g. ".control" or +-- "--.sql"). PostgreSQL truncates identifiers at NAMEDATALEN +-- (63 bytes), so a derived name longer than that would collide with the +-- extension's other artifacts once truncated. These must be rejected up +-- front rather than silently create colliding functions. + +-- An extension name that overflows the generated function name is rejected +-- and nothing is created. +SELECT pgtle.install_extension +( + 'test9greaterthanNAMEDATALENNAMEDATALENNAMEDATALENNAMEDATALENNAMEDATALENNAMEDATALEN', + '0.1', + 'comment', + $_pgtle_$ SELECT 1; $_pgtle_$ +); +SELECT count(*) AS leftover_funcs FROM pg_proc + WHERE pronamespace = 'pgtle'::regnamespace AND proname LIKE 'test9%'; + +-- A long version on a short name overflows the "--.sql" form and is +-- likewise rejected, both via install_extension and install_extension_version. +SELECT pgtle.install_extension +( + 'shortname', + '012345678901234567890123456789012345678901234567890123456789', + 'comment', + $_pgtle_$ SELECT 1; $_pgtle_$ +); +SELECT pgtle.install_extension('shortname', '1.0', 'comment', $_pgtle_$ SELECT 1; $_pgtle_$); +SELECT pgtle.install_extension_version_sql +( + 'shortname', + '012345678901234567890123456789012345678901234567890123456789', + $_pgtle_$ SELECT 1; $_pgtle_$ +); +-- ... and the "----.sql" update-path form too. +SELECT pgtle.install_update_path +( + 'shortname', + '1.0', + '012345678901234567890123456789012345678901234567890123456789', + $_pgtle_$ SELECT 1; $_pgtle_$ +); +SELECT pgtle.uninstall_extension('shortname'); + +-- A name at the limit (the generated ".sql" name is 63 bytes) still works. +SELECT pgtle.install_extension +( + 'n5456789012345678901234567890123456789012345678901234', + '1.0', + 'comment', + $_pgtle_$ SELECT 1; $_pgtle_$ +); +SELECT name FROM pgtle.available_extensions() + WHERE name = 'n5456789012345678901234567890123456789012345678901234'; +SELECT pgtle.uninstall_extension('n5456789012345678901234567890123456789012345678901234'); + -- Skip TransactionStmts BEGIN; SELECT pgtle.available_extension_versions();