From c87013f3bb2d09663cc16c5b08f564fe09293e28 Mon Sep 17 00:00:00 2001 From: "Chao Li (Evan)" Date: Mon, 9 Nov 2026 16:15:01 +0800 Subject: [PATCH v3 2/2] Reject relative paths consistently in extension_control_path The available-extension views accepted relative paths, while CREATE EXTENSION rejected them only when its search reached the relative element. Move the absolute-path check into get_extension_control_directories() so all callers validate the complete search path before using it. Replace the redundant check in find_in_paths() with an assertion. Add TAP coverage for the view and CREATE EXTENSION, including a case where an earlier valid directory contains the requested extension. 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 --- src/backend/commands/extension.c | 58 ++++++++++++------- .../t/001_extension_control_path.pl | 40 ++++++++++--- 2 files changed, 68 insertions(+), 30 deletions(-) diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 57bff97319a..52cbd4516e4 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -195,8 +195,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); /* * Return the extension location. If the current user doesn't have sufficient @@ -511,8 +510,11 @@ is_extension_script_filename(const char *filename) /* * Return a list of directories declared in the extension_control_path GUC. - * If skip_invalid_path is true, ignore empty elements instead of reporting an - * error. This is useful when looking for extensions only to provide a hint. + * All returned directory paths are absolute and canonicalized. + * + * If skip_invalid_path is true, ignore empty or relative elements instead of + * reporting an error. This is useful when looking for extensions only to + * provide a hint. */ static List * get_extension_control_directories(bool skip_invalid_path) @@ -526,6 +528,7 @@ get_extension_control_directories(bool skip_invalid_path) get_share_path(my_exec_path, sharepath); system_dir = psprintf("%s/extension", sharepath); + canonicalize_path(system_dir); if (strlen(Extension_control_path) == 0) { @@ -587,8 +590,23 @@ get_extension_control_directories(bool skip_invalid_path) /* Canonicalize the path based on the OS and add to the list */ canonicalize_path(mangled); - location->loc = mangled; - paths = lappend(paths, location); + if (!is_absolute_path(mangled)) + { + if (!skip_invalid_path) + ereport(ERROR, + errcode(ERRCODE_INVALID_NAME), + errmsg("component in parameter \"%s\" is not an absolute path", + "extension_control_path")); + pfree(mangled); + if (location->macro) + pfree(location->macro); + pfree(location); + } + else + { + location->loc = mangled; + paths = lappend(paths, location); + } } /* Break if ecp is empty or move to the next path on ecp */ @@ -4238,12 +4256,10 @@ new_ExtensionControlFile(const char *extname) } /* - * Search for the basename in the list of paths. - * - * Similar to find_in_path but for simplicity does not support custom error - * messages and expects 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; @@ -4251,26 +4267,26 @@ find_in_paths(const char *basename, List *paths) foreach(cell, paths) { ExtensionLocation *location = lfirst(cell); - char *path = location->loc; + const char *path = location->loc; char *full; Assert(path != NULL); +#ifdef USE_ASSERT_CHECKING + { + char *canonical = pstrdup(path); - 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")); + canonicalize_path(canonical); + Assert(strcmp(path, canonical) == 0); + pfree(canonical); + } +#endif + 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 94f9ae961a6..4f62a3756c8 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 @@ -131,21 +131,43 @@ for my $path ( "error identifies the empty path element"); } -# 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 $path ( + "$sep$ext_dir_canonicalized$sep", + "relative$sep$ext_dir_canonicalized${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"); + } +} + +# Reject relative paths even if an earlier directory contains the extension. +my $relative_ext = 'test_relative_path'; +create_extension($relative_ext, $ext_dir); for my $sql ( - "create function test_invalid_language() returns int language $ext_name as 'x'", - "do language $ext_name 'x'") + 'select count(*) from pg_available_extensions', + "create extension $relative_ext") { my ($code, $stdout, $stderr) = $node->psql( 'postgres', - "set extension_control_path = '$sep$ext_dir_canonicalized$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 = '$ext_dir_canonicalized${sep}relative'; + $sql"); + is($code, 3, "relative path is rejected by $sql"); like($stderr, - qr/HINT: Use CREATE EXTENSION to load the language into the database\./, - "remaining directory provides the extension hint"); + qr/ERROR: component in parameter "extension_control_path" is not an absolute path/, + "error identifies the relative path element"); } # Test with an extension that does not exists -- 2.50.1 (Apple Git-155)