From c086ce0c2c8fcfb418aff998ca9ffb1673f14bf5 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <lic@highgo.com>
Date: Tue, 3 Nov 2026 09:55:18 +0800
Subject: [PATCH v2 1/3] Reject empty elements in extension_control_path

Empty path elements were expanded to /extension by the automatic suffix
addition, causing an unintended search of that directory. Reject empty
elements before appending the suffix, so leading, trailing, and repeated
separators produce an error when the search path is used.

Preserve the documented $system default for an entirely empty setting.
Document the restriction and add TAP coverage for invalid empty elements.

Author: Chao Li <lic@highgo.com>
Reviewed-by: Prashant Chinnam <crprashant@hotmail.com>
Reviewed-by: Surya Poondla <suryapoondla4@gmail.com>
Discussion: https://postgr.es/m/DC2D9FA5-45C4-494F-99FC-F9B95AD4FF55@gmail.com
---
 doc/src/sgml/config.sgml                         |  1 +
 src/backend/commands/extension.c                 | 11 ++++++++++-
 .../t/001_extension_control_path.pl              | 16 ++++++++++++++++
 3 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index a6a2df177f2..8b36beb97f6 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -11783,6 +11783,7 @@ extension_control_path = 'C:\tools\postgresql;H:\my_project\share;$system'
         The default value for this parameter is
         <literal>'$system'</literal>. If the value is set to an empty
         string, the default <literal>'$system'</literal> is also assumed.
+        Empty elements within a nonempty value are not allowed.
        </para>
 
        <para>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 5855668c5ca..058ec18908b 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -541,7 +541,7 @@ get_extension_control_directories(void)
 			int			len;
 			char	   *mangled;
 			char	   *piece = first_path_var_separator(ecp);
-			ExtensionLocation *location = palloc_object(ExtensionLocation);
+			ExtensionLocation *location;
 
 			/* Get the length of the next path on ecp */
 			if (piece == NULL)
@@ -549,6 +549,15 @@ get_extension_control_directories(void)
 			else
 				len = piece - ecp;
 
+			/* Reject empty path elements before appending the suffix. */
+			if (len == 0)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_NAME),
+						errmsg("empty element in parameter \"%s\"",
+							   "extension_control_path"));
+
+			location = palloc_object(ExtensionLocation);
+
 			/* Copy the next path found on ecp */
 			piece = palloc(len + 1);
 			strlcpy(piece, ecp, len + 1);
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 4a013a7da4b..76c1d4e8223 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
@@ -115,6 +115,22 @@ is($ret, "\$system",
 	"\$system location is shown correctly in pg_available_extensions with empty extension_control_path"
 );
 
+# Empty path elements are rejected, rather than expanded to /extension.
+for my $path (
+	"$sep$ext_dir_canonicalized",
+	"$ext_dir_canonicalized$sep",
+	$sep)
+{
+	my ($code, $stdout, $stderr) = $node->psql(
+		'postgres',
+		"set extension_control_path = '$path';
+		 select count(*) from pg_available_extensions");
+	is($code, 3, "empty element is rejected in path '$path'");
+	like($stderr,
+		qr/ERROR:  empty element in parameter "extension_control_path"/,
+		"error identifies the empty path element");
+}
+
 # Test with an extension that does not exists
 my ($code, $stdout, $stderr) =
   $node->psql('postgres', "CREATE EXTENSION invalid");
-- 
2.50.1 (Apple Git-155)

