From 07ae6b317ba3e970cb98275f2556e296e7a91354 Mon Sep 17 00:00:00 2001 From: Konstantin Knizhnik Date: Thu, 19 Feb 2026 15:21:05 +0200 Subject: [PATCH 1/5] Prevent possible search_path attacks --- src/backend/catalog/namespace.c | 21 +++++++++++++++++++-- src/backend/utils/misc/guc_tables.c | 9 +++++++++ src/include/catalog/namespace.h | 1 + 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 66f196e96b2..a15ee826f06 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -210,7 +210,7 @@ static SubTransactionId myTempNamespaceSubID = InvalidSubTransactionId; * of the GUC variable 'search_path'. */ char *namespace_search_path = NULL; - +bool prohibit_superuser_overrides; /* Local functions */ static bool RelationIsVisibleExt(Oid relid, bool *is_missing); @@ -1201,6 +1201,7 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames, Oid namespaceId; CatCList *catlist; int i; + bool has_superuser_candidate = false; /* check for caller error */ Assert(nargs >= 0 || !(expand_variadic | expand_defaults)); @@ -1264,6 +1265,22 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames, continue; /* proc is not in search path */ } + /* prohibit overrides under superuser */ + if (prohibit_superuser_overrides && superuser()) + { + bool owned_by_superuser = superuser_arg(procform->proowner); + + /* If we have superuser condidate, then ignore all non-supoeruser alternatives */ + if (resultList && has_superuser_candidate && !owned_by_superuser) + continue; + + /* If new candidate is owned by superuser then forget all non-superuser candidates */ + if (owned_by_superuser && !has_superuser_candidate) + resultList = NULL; + + has_superuser_candidate = owned_by_superuser; + } + /* * If we are asked to match to OUT arguments, then use the * proallargtypes array (which includes those); otherwise use @@ -4245,7 +4262,7 @@ finalNamespacePath(List *oidlist, Oid *firstNS) * the front, not the back; also notice that we do not check USAGE * permissions for these. */ - if (!list_member_oid(finalPath, PG_CATALOG_NAMESPACE)) + if (!list_member_oid(finalPath, PG_CATALOG_NAMESPACE) || prohibit_superuser_overrides) finalPath = lcons_oid(PG_CATALOG_NAMESPACE, finalPath); if (OidIsValid(myTempNamespace) && diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 35b6696e26b..dd3bfa5fb94 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -2183,6 +2183,15 @@ struct config_bool ConfigureNamesBool[] = NULL, NULL, NULL }, + { + {"prohibit_superuser_overrides", PGC_SUSET, CLIENT_CONN_STATEMENT, + gettext_noop("Prevent overriding of functions defined by superuser by non-superuser candidates."), + }, + &prohibit_superuser_overrides, + false, + NULL, NULL, NULL + }, + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index 8c7ccc69a3c..fcd5006dc05 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -182,6 +182,7 @@ extern void AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid, /* stuff for search_path GUC variable */ extern PGDLLIMPORT char *namespace_search_path; +extern bool prohibit_superuser_overrides; extern List *fetch_search_path(bool includeImplicit); extern int fetch_search_path_array(Oid *sarray, int sarray_len); From 3a3c61c76bede1b1bc289ee52ed724668b2f5118 Mon Sep 17 00:00:00 2001 From: Konstantin Knizhnik Date: Tue, 3 Mar 2026 10:09:07 +0200 Subject: [PATCH 2/5] In case of prohibit_superuser_overrides always place pg_catalog at the beginning of search_path --- src/backend/catalog/namespace.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index a15ee826f06..6f1f4d9fa1d 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -4262,13 +4262,17 @@ finalNamespacePath(List *oidlist, Oid *firstNS) * the front, not the back; also notice that we do not check USAGE * permissions for these. */ - if (!list_member_oid(finalPath, PG_CATALOG_NAMESPACE) || prohibit_superuser_overrides) + if (!list_member_oid(finalPath, PG_CATALOG_NAMESPACE)) finalPath = lcons_oid(PG_CATALOG_NAMESPACE, finalPath); if (OidIsValid(myTempNamespace) && !list_member_oid(finalPath, myTempNamespace)) finalPath = lcons_oid(myTempNamespace, finalPath); + /* Always place pg_catalog at the beginning of search path */ + if (prohibit_superuser_overrides && superuser()) + finalPath = lcons_oid(PG_CATALOG_NAMESPACE, finalPath); + return finalPath; } From f1e5b965eedaeecdfb17457b89e1d58da45c69ed Mon Sep 17 00:00:00 2001 From: Konstantin Knizhnik Date: Tue, 3 Mar 2026 10:12:07 +0200 Subject: [PATCH 3/5] Enable prohibit_superuser_overrides by default --- src/backend/utils/misc/guc_tables.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index dd3bfa5fb94..7d64b4f973b 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -2188,7 +2188,7 @@ struct config_bool ConfigureNamesBool[] = gettext_noop("Prevent overriding of functions defined by superuser by non-superuser candidates."), }, &prohibit_superuser_overrides, - false, + true, NULL, NULL, NULL }, From 56863a1bf2d50c9f7aae51bd0533d8dd524f2457 Mon Sep 17 00:00:00 2001 From: Konstantin Knizhnik Date: Tue, 3 Mar 2026 16:24:30 +0200 Subject: [PATCH 4/5] Enable prohibit_superuser_overrides by default --- src/backend/catalog/namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 6f1f4d9fa1d..c24cd1a67c1 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -210,7 +210,7 @@ static SubTransactionId myTempNamespaceSubID = InvalidSubTransactionId; * of the GUC variable 'search_path'. */ char *namespace_search_path = NULL; -bool prohibit_superuser_overrides; +bool prohibit_superuser_overrides = true; /* Local functions */ static bool RelationIsVisibleExt(Oid relid, bool *is_missing); From 758c96f0879f1a15a86c261d71322706cf40af3f Mon Sep 17 00:00:00 2001 From: Konstantin Knizhnik Date: Tue, 3 Mar 2026 16:49:53 +0200 Subject: [PATCH 5/5] Enable prohibit_superuser_overrides by default --- src/test/regress/expected/stats.out | 2 +- src/test/regress/sql/stats.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 776f1ad0e53..891c1f407d0 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -955,7 +955,7 @@ SELECT wal_bytes > :backend_wal_bytes_before FROM pg_stat_get_backend_wal(pg_bac -- Test pg_stat_get_backend_idset() and some allied functions. -- In particular, verify that their notion of backend ID matches -- our temp schema index. -SELECT (current_schemas(true))[1] = ('pg_temp_' || beid::text) AS match +SELECT (current_schemas(true))[2] = ('pg_temp_' || beid::text) AS match FROM pg_stat_get_backend_idset() beid WHERE pg_stat_get_backend_pid(beid) = pg_backend_pid(); match diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 232ab8db8fa..7b5d76881a3 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -451,7 +451,7 @@ SELECT wal_bytes > :backend_wal_bytes_before FROM pg_stat_get_backend_wal(pg_bac -- Test pg_stat_get_backend_idset() and some allied functions. -- In particular, verify that their notion of backend ID matches -- our temp schema index. -SELECT (current_schemas(true))[1] = ('pg_temp_' || beid::text) AS match +SELECT (current_schemas(true))[2] = ('pg_temp_' || beid::text) AS match FROM pg_stat_get_backend_idset() beid WHERE pg_stat_get_backend_pid(beid) = pg_backend_pid();