From 2435d8b0bc35fb73f1a4f7fd23417421ebf50f62 Mon Sep 17 00:00:00 2001 From: "Chao Li (Evan)" Date: Mon, 9 Nov 2026 16:15:01 +0800 Subject: [PATCH v6 REL_18 2/2] Ignore relative elements in extension_control_path The available-extension views could list extensions found through relative paths, while CREATE EXTENSION rejected those paths when its search reached them. Discard relative elements in get_extension_control_directories() so all callers search only absolute paths. Extensions found only through relative elements are neither listed nor loaded, while valid absolute elements remain usable. Canonicalize system_dir in get_extension_control_directories() so all returned paths are canonicalized. Make find_in_paths() static and remove its redundant path copying and canonicalization, retaining an assertion that paths are absolute. Document the behavior and add TAP coverage using an actual relative directory, including checks that absolute directories remain usable with relative elements before or after them. Reported-by: Surya Poondla Author: Chao Li Reviewed-by: Surya Poondla Reviewed-by: Matheus Alcantara Discussion: https://postgr.es/m/DC2D9FA5-45C4-494F-99FC-F9B95AD4FF55@gmail.com --- doc/src/sgml/config.sgml | 1 + src/backend/commands/extension.c | 35 ++++------ .../t/001_extension_control_path.pl | 65 ++++++++++++++++--- 3 files changed, 69 insertions(+), 32 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 9a20dcf5422..e4f0e9dd662 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -11221,6 +11221,7 @@ extension_control_path = 'C:\tools\postgresql;H:\my_project\share;$system' '$system'. If the value is set to an empty string, the default '$system' is also assumed. Empty elements within a nonempty value are ignored. + Relative path elements are also ignored. diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 7bd5b3ab181..e8ff017fe48 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -177,8 +177,7 @@ static void ExecAlterExtensionContentsRecurse(AlterExtensionContentsStmt *stmt, ObjectAddress object); static char *read_whole_file(const char *filename, int *length); static ExtensionControlFile *new_ExtensionControlFile(const char *extname); - -char *find_in_paths(const char *basename, List *paths); +static char *find_in_paths(const char *basename, List *paths); /* * get_extension_oid - given an extension name, look up the OID @@ -470,7 +469,8 @@ is_extension_script_filename(const char *filename) /* * Return a list of directories declared on extension_control_path GUC. - * Empty elements are ignored. + * All returned directory paths are absolute and canonicalized. + * Empty and relative elements are ignored. */ static List * get_extension_control_directories(void) @@ -483,6 +483,7 @@ get_extension_control_directories(void) get_share_path(my_exec_path, sharepath); system_dir = psprintf("%s/extension", sharepath); + canonicalize_path(system_dir); if (strlen(Extension_control_path) == 0) { @@ -526,7 +527,10 @@ get_extension_control_directories(void) /* Canonicalize the path based on the OS and add to the list */ canonicalize_path(mangled); - paths = lappend(paths, mangled); + if (!is_absolute_path(mangled)) + pfree(mangled); + else + paths = lappend(paths, mangled); } /* Break if ecp is empty or move to the next path on ecp */ @@ -4164,40 +4168,27 @@ new_ExtensionControlFile(const char *extname) } /* - * Work in a very similar way with find_in_path but it receives an already - * parsed List of paths to search the basename and it do not support macro - * replacement or custom error messages (for simplicity). - * - * By "already parsed List of paths" this function expected that paths already - * have all macros replaced. + * Search for the basename in the list of absolute, canonicalized paths + * returned by get_extension_control_directories(). */ -char * +static char * find_in_paths(const char *basename, List *paths) { ListCell *cell; foreach(cell, paths) { - char *path = lfirst(cell); + const char *path = lfirst(cell); char *full; Assert(path != NULL); - - path = pstrdup(path); - canonicalize_path(path); - - /* only absolute paths */ - if (!is_absolute_path(path)) - ereport(ERROR, - errcode(ERRCODE_INVALID_NAME), - errmsg("component in parameter \"%s\" is not an absolute path", "extension_control_path")); + Assert(is_absolute_path(path)); full = psprintf("%s/%s", path, basename); if (pg_file_exists(full)) return full; - pfree(path); pfree(full); } diff --git a/src/test/modules/test_extensions/t/001_extension_control_path.pl b/src/test/modules/test_extensions/t/001_extension_control_path.pl index 8a2e3222059..4fef262ac6f 100644 --- a/src/test/modules/test_extensions/t/001_extension_control_path.pl +++ b/src/test/modules/test_extensions/t/001_extension_control_path.pl @@ -102,21 +102,66 @@ $ret = $node->safe_psql( select count(*) from pg_available_extensions"); is($ret, '0', 'separator-only path lists no extensions'); -# Empty elements must not replace an unknown-language error or prevent a hint +# Invalid elements must not replace an unknown-language error or prevent a hint # from being found using the remaining directories. -for my $sql ( - "create function test_invalid_language() returns int language $ext_name as 'x'", - "do language $ext_name 'x'") +for my $path ("$sep$ext_dir$sep", "relative$sep$ext_dir${sep}relative") +{ + for my $sql ( + "create function test_invalid_language() returns int language $ext_name as 'x'", + "do language $ext_name 'x'") + { + my ($code, $stdout, $stderr) = $node->psql( + 'postgres', + "set extension_control_path = '$path'; $sql"); + is($code, 3, "unknown language is rejected by $sql"); + like($stderr, qr/ERROR: language "\Q$ext_name\E" does not exist/, + "invalid path does not replace the unknown-language error"); + like($stderr, + qr/HINT: Use CREATE EXTENSION to load the language into the database\./, + "remaining directory provides the extension hint"); + } +} + +# An extension found only through a relative path must not be listed or loaded. +my $relative_dir = $node->data_dir . '/relative'; +mkpath("$relative_dir/extension"); +my $relative_ext = 'test_relative_path'; +create_extension($relative_ext, $relative_dir); +$ret = $node->safe_psql( + 'postgres', + "set extension_control_path = '\$system${sep}relative'; + select count(*) from pg_available_extensions where name = '$relative_ext'"); +is($ret, '0', 'extension in a relative directory is not listed'); + { my ($code, $stdout, $stderr) = $node->psql( 'postgres', - "set extension_control_path = '$sep$ext_dir$sep'; $sql"); - is($code, 3, "unknown language is rejected by $sql"); - like($stderr, qr/ERROR: language "\Q$ext_name\E" does not exist/, - "invalid path does not replace the unknown-language error"); + "set extension_control_path = '\$system${sep}relative'; + create extension $relative_ext"); + is($code, 3, 'extension in a relative directory cannot be loaded'); like($stderr, - qr/HINT: Use CREATE EXTENSION to load the language into the database\./, - "remaining directory provides the extension hint"); + qr/ERROR: extension "\Q$relative_ext\E" is not available/, + 'relative directory is excluded from the extension search'); +} + +# The same extension can be found and loaded through an absolute path, even +# with relative elements before or after that path. +create_extension($relative_ext, $ext_dir); +for my $path ("relative$sep$ext_dir", "$ext_dir${sep}relative") +{ + $ret = $node->safe_psql( + 'postgres', + "set extension_control_path = '$path'; + select count(*) from pg_available_extensions where name = '$relative_ext'"); + is($ret, '1', + "extension is listed from the absolute directory in path '$path'"); + $ret = $node->safe_psql( + 'postgres', + "set extension_control_path = '$path'; + create extension $relative_ext; + select count(*) from pg_extension where extname = '$relative_ext'; + drop extension $relative_ext"); + is($ret, '1', "extension can be loaded using path '$path'"); } # Test with an extension that does not exists -- 2.50.1 (Apple Git-155)