Hi solai,

On 22.09.26 06:13, solai v wrote:

[...]

> Overall, the patch worked as expected in my testing. The extension
> script now selects the trusted candidate instead of the attacker-owned
> candidate when both overloads are present.


Thank you so much for the review.


While testing the patch further, I found a gap with schema-qualified
names. In v1, the trust check for relations and types was applied only
when the script used an unqualified name and the lookup went through
RelnameGetRelid() or TypenameGetTypidExtended(). A reference such as
@[email protected] took the qualified branch of RangeVarGetRelidExtended()
or LookupTypeNameExtended() and was not checked at all. Functions and
operators were not affected, since their candidate lookup handles both
versions in one place.

v2 adds the check to those two branches, so an untrusted match is
treated as nonexistent there too. I also added tests for this case; I
slightly adjusted the commit message and rebased the patch set against
master.

Best regards
  Jan

-- 
Jan Nidzwetzki
PlanetScale Postgres Core Team
From e3709df506ec0a2d18c6390c4fba2723631374d0 Mon Sep 17 00:00:00 2001
From: Jan Nidzwetzki <[email protected]>
Date: Wed, 5 Aug 2026 15:21:09 +0200
Subject: [PATCH v2 1/2] Propagate extension-script state to parallel workers

creating_extension and CurrentExtensionObject are backend-local, so a
parallel worker doing work for a CREATE/ALTER EXTENSION script (for
example, executing a parallel-safe function's body) behaved as though no
script were in progress.

No visible effect on its own; preparation for the following commit.

Author: Jan Nidzwetzki <[email protected]>
Reviewed-by: Osama Abdul Qader <[email protected]>
Reviewed-by: solai v <[email protected]>
Discussion: 
https://postgr.es/m/[email protected]
---
 src/backend/access/transam/parallel.c |  9 +++++++++
 src/backend/commands/extension.c      | 24 ++++++++++++++++++++++++
 src/include/commands/extension.h      |  3 +++
 3 files changed, 36 insertions(+)

diff --git a/src/backend/access/transam/parallel.c 
b/src/backend/access/transam/parallel.c
index e1806a9a28a..6efa656d399 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -26,6 +26,7 @@
 #include "catalog/pg_enum.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/extension.h"
 #include "commands/vacuum.h"
 #include "executor/execParallel.h"
 #include "libpq/libpq.h"
@@ -90,9 +91,11 @@ typedef struct FixedParallelState
        Oid                     current_user_id;
        Oid                     temp_namespace_id;
        Oid                     temp_toast_namespace_id;
+       Oid                     current_extension_object;
        int                     sec_context;
        bool            session_user_is_superuser;
        bool            role_is_superuser;
+       bool            creating_extension;
        PGPROC     *parallel_leader_pgproc;
        pid_t           parallel_leader_pid;
        ProcNumber      parallel_leader_proc_number;
@@ -348,6 +351,8 @@ InitializeParallelDSM(ParallelContext *pcxt)
        fps->role_is_superuser = current_role_is_superuser;
        GetTempNamespaceState(&fps->temp_namespace_id,
                                                  
&fps->temp_toast_namespace_id);
+       GetExtensionCreationState(&fps->creating_extension,
+                                                         
&fps->current_extension_object);
        fps->parallel_leader_pgproc = MyProc;
        fps->parallel_leader_pid = MyProcPid;
        fps->parallel_leader_proc_number = MyProcNumber;
@@ -1534,6 +1539,10 @@ ParallelWorkerMain(Datum main_arg)
        SetTempNamespaceState(fps->temp_namespace_id,
                                                  fps->temp_toast_namespace_id);
 
+       /* Restore extension-script state, so the worker matches the leader. */
+       SetExtensionCreationState(fps->creating_extension,
+                                                         
fps->current_extension_object);
+
        /* Restore uncommitted enums. */
        uncommittedenumsspace = shm_toc_lookup(toc, 
PARALLEL_KEY_UNCOMMITTEDENUMS,
                                                                                
   false);
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 5855668c5ca..f8f6b179631 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1509,6 +1509,30 @@ execute_extension_script(Oid extensionOid, 
ExtensionControlFile *control,
                SetUserIdAndSecContext(save_userid, save_sec_context);
 }
 
+/*
+ * GetExtensionCreationState - report the current extension-script state
+ *
+ * Used by the parallel-query machinery to carry creating_extension and
+ * CurrentExtensionObject to workers, so a worker sees the same
+ * extension-script state as the leader.
+ */
+void
+GetExtensionCreationState(bool *creating, Oid *extensionObject)
+{
+       *creating = creating_extension;
+       *extensionObject = CurrentExtensionObject;
+}
+
+/*
+ * SetExtensionCreationState - restore extension-script state in a worker
+ */
+void
+SetExtensionCreationState(bool creating, Oid extensionObject)
+{
+       creating_extension = creating;
+       CurrentExtensionObject = extensionObject;
+}
+
 /*
  * Find or create an ExtensionVersionInfo for the specified version name
  *
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 7a76bdebcfa..8eaec2d4f68 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -32,6 +32,9 @@ extern PGDLLIMPORT char *Extension_control_path;
 extern PGDLLIMPORT bool creating_extension;
 extern PGDLLIMPORT Oid CurrentExtensionObject;
 
+extern void GetExtensionCreationState(bool *creating, Oid *extensionObject);
+extern void SetExtensionCreationState(bool creating, Oid extensionObject);
+
 
 extern ObjectAddress CreateExtension(ParseState *pstate, CreateExtensionStmt 
*stmt);
 
-- 
2.47.3

From 3bfc26ddf25a6eba6fd9ef3f6d6caf6eeae2884e Mon Sep 17 00:00:00 2001
From: Jan Nidzwetzki <[email protected]>
Date: Wed, 5 Aug 2026 15:21:09 +0200
Subject: [PATCH v2 2/2] Prefer trusted candidates when resolving names in
 extension scripts

An extension script runs with superuser privileges, so a user who can
create objects in the extension's schema can plant one that captures a
reference the script makes, f(text) beside the extension's f(varchar)
or a domain shadowing a required extension's, and run code with those
privileges.  Neither pinning search_path nor writing @[email protected]
helps: the plant is in the script's own schema, so a qualified name
reaches it just as a search_path lookup does.

Ignore untrusted objects while a script runs.  Trusted means in
pg_catalog, owned by a superuser, owned by the role running the script,
or a member of the extension being installed or of one it requires.  The
relation, type, function and operator lookups all apply the test, for
schema-qualified names as well as names resolved through search_path;
the last two apply it as they gather candidates so a plant cannot
displace a trusted match by search-path position.  Resolution otherwise
fails as if the object did not exist, with a detail saying why.  Cached
plans record whether they were analyzed inside a script, since a
script's search_path can match one the session already had.

Reported-by: Mehmet Ince (@mdisec)
Author: Jan Nidzwetzki <[email protected]>
Reviewed-by: Osama Abdul Qader <[email protected]>
Reviewed-by: solai v <[email protected]>
Discussion: 
https://postgr.es/m/[email protected]
---
 src/backend/catalog/namespace.c               | 308 +++++++++++++---
 src/backend/catalog/pg_operator.c             |  18 +
 src/backend/commands/extension.c              |  51 +++
 src/backend/commands/functioncmds.c           |   4 +-
 src/backend/parser/parse_func.c               |  21 +-
 src/backend/parser/parse_oper.c               |  33 +-
 src/backend/parser/parse_relation.c           |   6 +-
 src/backend/parser/parse_type.c               |  32 +-
 src/backend/utils/cache/plancache.c           |  29 ++
 src/include/catalog/namespace.h               |   8 +
 src/include/commands/extension.h              |   1 +
 src/include/parser/parse_type.h               |   1 +
 src/include/utils/plancache.h                 |   1 +
 src/test/modules/test_extensions/Makefile     |  20 +
 .../expected/test_extensions.out              | 348 ++++++++++++++++++
 src/test/modules/test_extensions/meson.build  |  22 ++
 .../test_extensions/sql/test_extensions.sql   | 194 ++++++++++
 .../test_ext_overload--1.0.sql                |  19 +
 .../test_extensions/test_ext_overload.control |   3 +
 .../test_ext_overload_nosuper--1.0--2.0.sql   |   9 +
 .../test_ext_overload_nosuper--1.0.sql        |  12 +
 .../test_ext_overload_nosuper.control         |   4 +
 .../test_ext_overload_parallel--1.0.sql       |  15 +
 .../test_ext_overload_parallel.control        |   3 +
 .../test_ext_overload_req--1.0.sql            |  20 +
 .../test_ext_overload_req.control             |   4 +
 .../test_ext_overload_req_dep--1.0.sql        |  23 ++
 .../test_ext_overload_req_dep.control         |   4 +
 .../test_ext_overload_strict--1.0.sql         |   9 +
 .../test_ext_overload_strict--10.0.sql        |   9 +
 .../test_ext_overload_strict--2.0.sql         |   9 +
 .../test_ext_overload_strict--3.0.sql         |  10 +
 .../test_ext_overload_strict--4.0.sql         |   9 +
 .../test_ext_overload_strict--5.0.sql         |   8 +
 .../test_ext_overload_strict--6.0.sql         |   8 +
 .../test_ext_overload_strict--7.0.sql         |  12 +
 .../test_ext_overload_strict--8.0.sql         |   8 +
 .../test_ext_overload_strict--9.0.sql         |   7 +
 .../test_ext_overload_strict.control          |   3 +
 39 files changed, 1250 insertions(+), 55 deletions(-)
 create mode 100644 src/test/modules/test_extensions/test_ext_overload--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_overload.control
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_nosuper--1.0--2.0.sql
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_nosuper--1.0.sql
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_nosuper.control
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_parallel--1.0.sql
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_parallel.control
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_req--1.0.sql
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_req.control
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_req_dep--1.0.sql
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_req_dep.control
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_strict--1.0.sql
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_strict--10.0.sql
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_strict--2.0.sql
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_strict--3.0.sql
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_strict--4.0.sql
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_strict--5.0.sql
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_strict--6.0.sql
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_strict--7.0.sql
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_strict--8.0.sql
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_strict--9.0.sql
 create mode 100644 
src/test/modules/test_extensions/test_ext_overload_strict.control

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 0647a198dea..e0569438e43 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -41,6 +41,7 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
+#include "commands/extension.h"
 #include "common/hashfn_unstable.h"
 #include "funcapi.h"
 #include "mb/pg_wchar.h"
@@ -225,6 +226,7 @@ static bool TSParserIsVisibleExt(Oid prsId, bool 
*is_missing);
 static bool TSDictionaryIsVisibleExt(Oid dictId, bool *is_missing);
 static bool TSTemplateIsVisibleExt(Oid tmplId, bool *is_missing);
 static bool TSConfigIsVisibleExt(Oid cfgid, bool *is_missing);
+static bool RelationIsTrustedInExtensionScript(Oid relid);
 static void recomputeNamespacePath(void);
 static void AccessTempTableNamespace(bool force);
 static void InitTempTableNamespace(void);
@@ -533,6 +535,14 @@ RangeVarGetRelidExtended(const RangeVar *relation, 
LOCKMODE lockmode,
                                relId = InvalidOid;
                        else
                                relId = get_relname_relid(relation->relname, 
namespaceId);
+
+                       /*
+                        * Treat an untrusted match as nonexistent while an 
extension
+                        * script runs, as RelnameGetRelid does for unqualified 
names.
+                        */
+                       if (OidIsValid(relId) && creating_extension &&
+                               !RelationIsTrustedInExtensionScript(relId))
+                               relId = InvalidOid;
                }
                else
                {
@@ -632,12 +642,14 @@ RangeVarGetRelidExtended(const RangeVar *relation, 
LOCKMODE lockmode,
                        ereport(elevel,
                                        (errcode(ERRCODE_UNDEFINED_TABLE),
                                         errmsg("relation \"%s.%s\" does not 
exist",
-                                                       relation->schemaname, 
relation->relname)));
+                                                       relation->schemaname, 
relation->relname),
+                                        
errdetail_untrusted_relation(relation)));
                else
                        ereport(elevel,
                                        (errcode(ERRCODE_UNDEFINED_TABLE),
                                         errmsg("relation \"%s\" does not 
exist",
-                                                       relation->relname)));
+                                                       relation->relname),
+                                        
errdetail_untrusted_relation(relation)));
        }
        return relId;
 }
@@ -896,13 +908,41 @@ RelnameGetRelid(const char *relname)
 
                relid = get_relname_relid(relname, namespaceId);
                if (OidIsValid(relid))
+               {
+                       /* Skip untrusted matches while an extension script 
runs */
+                       if (creating_extension &&
+                               !RelationIsTrustedInExtensionScript(relid))
+                               continue;
                        return relid;
+               }
        }
 
        /* Not found in path */
        return InvalidOid;
 }
 
+/*
+ * RelationIsTrustedInExtensionScript
+ *             ObjectIsTrustedInExtensionScript for a relation, by OID.
+ */
+static bool
+RelationIsTrustedInExtensionScript(Oid relid)
+{
+       HeapTuple       tp;
+       Form_pg_class form;
+       bool            result;
+
+       tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+       if (!HeapTupleIsValid(tp))
+               return true;
+       form = (Form_pg_class) GETSTRUCT(tp);
+       result = ObjectIsTrustedInExtensionScript(RelationRelationId, relid,
+                                                                               
          form->relnamespace,
+                                                                               
          form->relowner);
+       ReleaseSysCache(tp);
+       return result;
+}
+
 
 /*
  * RelationIsVisible
@@ -1024,13 +1064,42 @@ TypenameGetTypidExtended(const char *typname, bool 
temp_ok)
                                                                
PointerGetDatum(typname),
                                                                
ObjectIdGetDatum(namespaceId));
                if (OidIsValid(typid))
+               {
+                       /* Skip untrusted matches while an extension script 
runs */
+                       if (creating_extension && 
!TypeIsTrustedInExtensionScript(typid))
+                               continue;
                        return typid;
+               }
        }
 
        /* Not found in path */
        return InvalidOid;
 }
 
+/*
+ * TypeIsTrustedInExtensionScript
+ *             ObjectIsTrustedInExtensionScript for a type, by OID.
+ *
+ * Also used by LookupTypeName, which resolves qualified names itself.
+ */
+bool
+TypeIsTrustedInExtensionScript(Oid typid)
+{
+       HeapTuple       tp;
+       Form_pg_type form;
+       bool            result;
+
+       tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
+       if (!HeapTupleIsValid(tp))
+               return true;
+       form = (Form_pg_type) GETSTRUCT(tp);
+       result = ObjectIsTrustedInExtensionScript(TypeRelationId, typid,
+                                                                               
          form->typnamespace,
+                                                                               
          form->typowner);
+       ReleaseSysCache(tp);
+       return result;
+}
+
 /*
  * TypeIsVisible
  *             Determine whether a type (identified by OID) is visible in the
@@ -1278,6 +1347,21 @@ FuncnameGetCandidates(List *names, int nargs, List 
*argnames,
                                continue;               /* proc is not in 
search path */
                }
 
+               /*
+                * During an extension script, skip untrusted candidates before 
any
+                * further flags are set, so the remaining flags describe 
trusted
+                * candidates only (see ObjectIsTrustedInExtensionScript).
+                */
+               if (creating_extension &&
+                       !ObjectIsTrustedInExtensionScript(ProcedureRelationId,
+                                                                               
          procform->oid,
+                                                                               
          procform->pronamespace,
+                                                                               
          procform->proowner))
+               {
+                       *fgc_flags |= FGC_UNTRUSTED_SKIP;
+                       continue;
+               }
+
                *fgc_flags |= FGC_NAME_VISIBLE; /* routine is in the right 
schema */
 
                /*
@@ -1591,6 +1675,118 @@ FuncnameGetCandidates(List *names, int nargs, List 
*argnames,
        return resultList;
 }
 
+/*
+ * ObjectIsTrustedInExtensionScript
+ *             May an extension script safely resolve a name to this object?
+ *
+ * Trusted means in pg_catalog, owned by a superuser, owned by the role running
+ * the script, or a member of the extension being installed or of one it
+ * requires.  The membership rule lets a script reach objects that an earlier
+ * version of itself, or a "superuser = false" required extension, created
+ * under some other role.
+ */
+bool
+ObjectIsTrustedInExtensionScript(Oid classId, Oid objectId,
+                                                                Oid 
namespaceId, Oid ownerId)
+{
+       Oid                     extensionId;
+
+       if (namespaceId == PG_CATALOG_NAMESPACE ||
+               superuser_arg(ownerId) ||
+               ownerId == GetUserId())
+               return true;
+
+       extensionId = getExtensionOfObject(classId, objectId);
+       if (!OidIsValid(extensionId))
+               return false;
+
+       return extensionId == CurrentExtensionObject ||
+               CurrentExtensionRequires(extensionId);
+}
+
+/*
+ * errdetail_untrusted_relation
+ *             Explain a failed relation lookup during an extension script, if 
a
+ *             relation of that name exists but is not trusted.
+ */
+int
+errdetail_untrusted_relation(const RangeVar *relation)
+{
+       Oid                     relid = InvalidOid;
+
+       if (!creating_extension)
+               return 0;
+
+       if (relation->schemaname)
+       {
+               Oid                     namespaceId = 
get_namespace_oid(relation->schemaname, true);
+
+               if (OidIsValid(namespaceId))
+                       relid = get_relname_relid(relation->relname, 
namespaceId);
+       }
+       else
+       {
+               ListCell   *l;
+
+               recomputeNamespacePath();
+               foreach(l, activeSearchPath)
+               {
+                       relid = get_relname_relid(relation->relname, 
lfirst_oid(l));
+                       if (OidIsValid(relid))
+                               break;
+               }
+       }
+
+       if (!OidIsValid(relid) || RelationIsTrustedInExtensionScript(relid))
+               return 0;
+
+       errdetail("A relation of that name exists, but it is not trusted while 
an extension script runs.");
+       return errhint("Only objects in pg_catalog, owned by a superuser, or 
belonging to the extension or one it requires are trusted.");
+}
+
+/*
+ * errdetail_untrusted_type
+ *             Likewise for a type, given its schema (or NULL) and name.
+ */
+int
+errdetail_untrusted_type(const char *schemaname, const char *typname)
+{
+       Oid                     typid = InvalidOid;
+
+       if (!creating_extension)
+               return 0;
+
+       if (schemaname)
+       {
+               Oid                     namespaceId = 
get_namespace_oid(schemaname, true);
+
+               if (OidIsValid(namespaceId))
+                       typid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
+                                                                       
PointerGetDatum(typname),
+                                                                       
ObjectIdGetDatum(namespaceId));
+       }
+       else
+       {
+               ListCell   *l;
+
+               recomputeNamespacePath();
+               foreach(l, activeSearchPath)
+               {
+                       typid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
+                                                                       
PointerGetDatum(typname),
+                                                                       
ObjectIdGetDatum(lfirst_oid(l)));
+                       if (OidIsValid(typid))
+                               break;
+               }
+       }
+
+       if (!OidIsValid(typid) || TypeIsTrustedInExtensionScript(typid))
+               return 0;
+
+       errdetail("A type of that name exists, but it is not trusted while an 
extension script runs.");
+       return errhint("Only objects in pg_catalog, owned by a superuser, or 
belonging to the extension or one it requires are trusted.");
+}
+
 /*
  * MatchNamedCall
  *             Given a pg_proc heap tuple and a call's list of argument names,
@@ -1861,6 +2057,14 @@ OpernameGetOprid(List *names, Oid oprleft, Oid oprright)
                                Form_pg_operator operclass = (Form_pg_operator) 
GETSTRUCT(opertup);
                                Oid                     result = operclass->oid;
 
+                               /* Reject an untrusted match while an extension 
script runs */
+                               if (creating_extension &&
+                                       
!ObjectIsTrustedInExtensionScript(OperatorRelationId,
+                                                                               
                          result,
+                                                                               
                          operclass->oprnamespace,
+                                                                               
                          operclass->oprowner))
+                                       result = InvalidOid;
+
                                ReleaseSysCache(opertup);
                                return result;
                        }
@@ -1906,6 +2110,14 @@ OpernameGetOprid(List *names, Oid oprleft, Oid oprright)
                        {
                                Oid                     result = operform->oid;
 
+                               /* Skip untrusted matches while an extension 
script runs */
+                               if (creating_extension &&
+                                       
!ObjectIsTrustedInExtensionScript(OperatorRelationId,
+                                                                               
                          result,
+                                                                               
                          operform->oprnamespace,
+                                                                               
                          operform->oprowner))
+                                       continue;
+
                                ReleaseSysCacheList(catlist);
                                return result;
                        }
@@ -2033,53 +2245,63 @@ OpernameGetCandidates(List *names, char oprkind, bool 
missing_schema_ok,
                        }
                        if (nsp == NULL)
                                continue;               /* oper is not in 
search path */
+               }
 
-                       /*
-                        * Okay, it's in the search path, but does it have the 
same
-                        * arguments as something we already accepted?  If so, 
keep only
-                        * the one that appears earlier in the search path.
-                        *
-                        * If we have an ordered list from SearchSysCacheList 
(the normal
-                        * case), then any conflicting oper must immediately 
adjoin this
-                        * one in the list, so we only need to look at the 
newest result
-                        * item.  If we have an unordered list, we have to scan 
the whole
-                        * result list.
-                        */
-                       if (resultList)
-                       {
-                               FuncCandidateList prevResult;
+               /* Likewise skip untrusted candidates, as in 
FuncnameGetCandidates */
+               if (creating_extension &&
+                       !ObjectIsTrustedInExtensionScript(OperatorRelationId,
+                                                                               
          operform->oid,
+                                                                               
          operform->oprnamespace,
+                                                                               
          operform->oprowner))
+               {
+                       *fgc_flags |= FGC_UNTRUSTED_SKIP;
+                       continue;
+               }
 
-                               if (catlist->ordered)
-                               {
-                                       if (operform->oprleft == 
resultList->args[0] &&
-                                               operform->oprright == 
resultList->args[1])
-                                               prevResult = resultList;
-                                       else
-                                               prevResult = NULL;
-                               }
+               /*
+                * Okay, it's in the search path, but does it have the same 
arguments
+                * as something we already accepted?  If so, keep only the one 
that
+                * appears earlier in the search path.
+                *
+                * If we have an ordered list from SearchSysCacheList (the 
normal
+                * case), then any conflicting oper must immediately adjoin 
this one
+                * in the list, so we only need to look at the newest result 
item.  If
+                * we have an unordered list, we have to scan the whole result 
list.
+                */
+               if (!OidIsValid(namespaceId) && resultList)
+               {
+                       FuncCandidateList prevResult;
+
+                       if (catlist->ordered)
+                       {
+                               if (operform->oprleft == resultList->args[0] &&
+                                       operform->oprright == 
resultList->args[1])
+                                       prevResult = resultList;
                                else
+                                       prevResult = NULL;
+                       }
+                       else
+                       {
+                               for (prevResult = resultList;
+                                        prevResult;
+                                        prevResult = prevResult->next)
                                {
-                                       for (prevResult = resultList;
-                                                prevResult;
-                                                prevResult = prevResult->next)
-                                       {
-                                               if (operform->oprleft == 
prevResult->args[0] &&
-                                                       operform->oprright == 
prevResult->args[1])
-                                                       break;
-                                       }
-                               }
-                               if (prevResult)
-                               {
-                                       /* We have a match with a previous 
result */
-                                       Assert(pathpos != prevResult->pathpos);
-                                       if (pathpos > prevResult->pathpos)
-                                               continue;       /* keep 
previous result */
-                                       /* replace previous result */
-                                       prevResult->pathpos = pathpos;
-                                       prevResult->oid = operform->oid;
-                                       continue;       /* args are same, of 
course */
+                                       if (operform->oprleft == 
prevResult->args[0] &&
+                                               operform->oprright == 
prevResult->args[1])
+                                               break;
                                }
                        }
+                       if (prevResult)
+                       {
+                               /* We have a match with a previous result */
+                               Assert(pathpos != prevResult->pathpos);
+                               if (pathpos > prevResult->pathpos)
+                                       continue;       /* keep previous result 
*/
+                               /* replace previous result */
+                               prevResult->pathpos = pathpos;
+                               prevResult->oid = operform->oid;
+                               continue;               /* args are same, of 
course */
+                       }
                }
 
                *fgc_flags |= FGC_NAME_VISIBLE; /* operator is in the right 
schema */
diff --git a/src/backend/catalog/pg_operator.c 
b/src/backend/catalog/pg_operator.c
index 6b90c774c18..37c7d2e6c5c 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "commands/extension.h"
 #include "miscadmin.h"
 #include "parser/parse_oper.h"
 #include "utils/acl.h"
@@ -643,6 +644,23 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid 
otherRightTypeId,
        otherNamespace = QualifiedNameGetCreationNamespace(otherOp,
                                                                                
                           &otherName);
 
+       /*
+        * If the lookup failed only because the operator is untrusted during an
+        * extension script, say so rather than colliding with it below.
+        */
+       if (creating_extension &&
+               OidIsValid(OperatorGet(otherName, otherNamespace,
+                                                          otherLeftTypeId, 
otherRightTypeId,
+                                                          &otherDefined)))
+               ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_FUNCTION),
+                                errmsg("operator does not exist: %s",
+                                               op_signature_string(otherOp,
+                                                                               
        otherLeftTypeId,
+                                                                               
        otherRightTypeId)),
+                                errdetail("An operator of that name exists, 
but it is not trusted while an extension script runs."),
+                                errhint("Only objects in pg_catalog, owned by 
a superuser, or belonging to the extension or one it requires are trusted.")));
+
        if (strcmp(otherName, operatorName) == 0 &&
                otherNamespace == operatorNamespace &&
                otherLeftTypeId == leftTypeId &&
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index f8f6b179631..4874bf596b7 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1533,6 +1533,57 @@ SetExtensionCreationState(bool creating, Oid 
extensionObject)
        CurrentExtensionObject = extensionObject;
 }
 
+/*
+ * CurrentExtensionRequires - does the running script's extension require this
+ * extension?
+ *
+ * Only direct requirements count; those are the ones whose schemas
+ * execute_extension_script puts into the script's search path.
+ */
+bool
+CurrentExtensionRequires(Oid extensionId)
+{
+       Relation        depRel;
+       ScanKeyData key[2];
+       SysScanDesc depScan;
+       HeapTuple       depTup;
+       bool            result = false;
+
+       if (!OidIsValid(CurrentExtensionObject))
+               return false;
+
+       depRel = table_open(DependRelationId, AccessShareLock);
+
+       ScanKeyInit(&key[0],
+                               Anum_pg_depend_classid,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(ExtensionRelationId));
+       ScanKeyInit(&key[1],
+                               Anum_pg_depend_objid,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(CurrentExtensionObject));
+
+       depScan = systable_beginscan(depRel, DependDependerIndexId, true,
+                                                                NULL, 2, key);
+
+       while (HeapTupleIsValid(depTup = systable_getnext(depScan)))
+       {
+               Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup);
+
+               if (pg_depend->refclassid == ExtensionRelationId &&
+                       pg_depend->refobjid == extensionId)
+               {
+                       result = true;
+                       break;
+               }
+       }
+
+       systable_endscan(depScan);
+       table_close(depRel, AccessShareLock);
+
+       return result;
+}
+
 /*
  * Find or create an ExtensionVersionInfo for the specified version name
  *
diff --git a/src/backend/commands/functioncmds.c 
b/src/backend/commands/functioncmds.c
index 4d46013d7de..b13f1e37bc1 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -140,7 +140,8 @@ compute_return_type(TypeName *returnType, Oid languageOid,
                ereport(ERROR,
                                (errcode(ERRCODE_UNDEFINED_OBJECT),
                                 errmsg("type \"%s\" does not exist",
-                                               TypeNameToString(returnType))));
+                                               TypeNameToString(returnType)),
+                                errdetail_untrusted_typename(returnType)));
        }
        else
        {
@@ -282,6 +283,7 @@ interpret_function_parameter_list(ParseState *pstate,
                                        (errcode(ERRCODE_UNDEFINED_OBJECT),
                                         errmsg("type %s does not exist",
                                                        TypeNameToString(t)),
+                                        errdetail_untrusted_typename(t),
                                         parser_errposition(pstate, 
t->location)));
                        toid = InvalidOid;      /* keep compiler quiet */
                }
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index f75f5f2fbba..df181a3e9a7 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -1003,7 +1003,15 @@ func_lookup_failure_details(int fgc_flags, List 
*argnames, bool proc_call)
         */
        if (!(fgc_flags & FGC_NAME_VISIBLE))
        {
-               if (fgc_flags & FGC_SCHEMA_GIVEN)
+               if (fgc_flags & FGC_UNTRUSTED_SKIP)
+               {
+                       if (proc_call)
+                               (void) errdetail("A procedure of that name 
exists, but it is not trusted while an extension script runs.");
+                       else
+                               (void) errdetail("A function of that name 
exists, but it is not trusted while an extension script runs.");
+                       return errhint("Only objects in pg_catalog, owned by a 
superuser, or belonging to the extension or one it requires are trusted.");
+               }
+               else if (fgc_flags & FGC_SCHEMA_GIVEN)
                        return 0;                       /* schema-qualified 
name */
                else if (!(fgc_flags & FGC_NAME_EXISTS))
                {
@@ -1021,6 +1029,15 @@ func_lookup_failure_details(int fgc_flags, List 
*argnames, bool proc_call)
                }
        }
 
+       /* A trusted candidate was visible; mention any skipped one as a hint */
+       if (fgc_flags & FGC_UNTRUSTED_SKIP)
+       {
+               if (proc_call)
+                       (void) errhint("A procedure of that name was ignored 
because it is not trusted while an extension script runs.");
+               else
+                       (void) errhint("A function of that name was ignored 
because it is not trusted while an extension script runs.");
+       }
+
        /*
         * Next, complain if nothing had the right number of arguments.  (This
         * takes precedence over wrong-argnames cases because we won't even look
@@ -1076,6 +1093,8 @@ func_lookup_failure_details(int fgc_flags, List 
*argnames, bool proc_call)
                (void) errdetail("No procedure of that name accepts the given 
argument types.");
        else
                (void) errdetail("No function of that name accepts the given 
argument types.");
+       if (fgc_flags & FGC_UNTRUSTED_SKIP)
+               return 0;                               /* keep the hint set 
above */
        return errhint("You might need to add explicit type casts.");
 }
 
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index dc0f047ca25..a97422786d3 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -16,8 +16,10 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_type.h"
+#include "commands/extension.h"
 #include "lib/stringinfo.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_coerce.h"
@@ -388,6 +390,13 @@ oper(ParseState *pstate, List *opname, Oid ltypeId, Oid 
rtypeId,
         */
        key_ok = make_oper_cache_key(pstate, &key, opname, ltypeId, rtypeId, 
location);
 
+       /*
+        * Skip the lookaside cache during an extension script, so the trust
+        * checks below see the catalog state.
+        */
+       if (creating_extension)
+               key_ok = false;
+
        if (key_ok)
        {
                operOid = find_oper_cache_entry(&key);
@@ -540,6 +549,10 @@ left_oper(ParseState *pstate, List *op, Oid arg, bool 
noError, int location)
         */
        key_ok = make_oper_cache_key(pstate, &key, op, InvalidOid, arg, 
location);
 
+       /* Skip the lookaside cache during an extension script; see oper() */
+       if (creating_extension)
+               key_ok = false;
+
        if (key_ok)
        {
                operOid = find_oper_cache_entry(&key);
@@ -672,7 +685,12 @@ oper_lookup_failure_details(int fgc_flags, bool 
is_unary_op)
         */
        if (!(fgc_flags & FGC_NAME_VISIBLE))
        {
-               if (fgc_flags & FGC_SCHEMA_GIVEN)
+               if (fgc_flags & FGC_UNTRUSTED_SKIP)
+               {
+                       (void) errdetail("An operator of that name exists, but 
it is not trusted while an extension script runs.");
+                       return errhint("Only objects in pg_catalog, owned by a 
superuser, or belonging to the extension or one it requires are trusted.");
+               }
+               else if (fgc_flags & FGC_SCHEMA_GIVEN)
                        return 0;                       /* schema-qualified 
name */
                else if (!(fgc_flags & FGC_NAME_EXISTS))
                        return errdetail("There is no operator of that name.");
@@ -681,18 +699,19 @@ oper_lookup_failure_details(int fgc_flags, bool 
is_unary_op)
        }
 
        /*
-        * Otherwise, the problem must be incorrect argument type(s).
+        * Otherwise, the problem must be incorrect argument type(s); mention 
any
+        * skipped untrusted candidate in place of the usual hint.
         */
        if (is_unary_op)
-       {
                (void) errdetail("No operator of that name accepts the given 
argument type.");
-               return errhint("You might need to add an explicit type cast.");
-       }
        else
-       {
                (void) errdetail("No operator of that name accepts the given 
argument types.");
+       if (fgc_flags & FGC_UNTRUSTED_SKIP)
+               return errhint("An operator of that name was ignored because it 
is not trusted while an extension script runs.");
+       else if (is_unary_op)
+               return errhint("You might need to add an explicit type cast.");
+       else
                return errhint("You might need to add explicit type casts.");
-       }
 }
 
 /*
diff --git a/src/backend/parser/parse_relation.c 
b/src/backend/parser/parse_relation.c
index 8aeb3e219ac..857652488f8 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1467,7 +1467,8 @@ parserOpenTable(ParseState *pstate, const RangeVar 
*relation, LOCKMODE lockmode)
                        ereport(ERROR,
                                        (errcode(ERRCODE_UNDEFINED_TABLE),
                                         errmsg("relation \"%s.%s\" does not 
exist",
-                                                       relation->schemaname, 
relation->relname)));
+                                                       relation->schemaname, 
relation->relname),
+                                        
errdetail_untrusted_relation(relation)));
                else
                {
                        /*
@@ -1488,7 +1489,8 @@ parserOpenTable(ParseState *pstate, const RangeVar 
*relation, LOCKMODE lockmode)
                                ereport(ERROR,
                                                
(errcode(ERRCODE_UNDEFINED_TABLE),
                                                 errmsg("relation \"%s\" does 
not exist",
-                                                               
relation->relname)));
+                                                               
relation->relname),
+                                                
errdetail_untrusted_relation(relation)));
                }
        }
        cancel_parser_errposition_callback(&pcbstate);
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 262f2742b02..ab34617ec4e 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -17,6 +17,7 @@
 #include "access/htup_details.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
+#include "commands/extension.h"
 #include "lib/stringinfo.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_type.h"
@@ -178,9 +179,19 @@ LookupTypeNameExtended(ParseState *pstate,
 
                        namespaceId = LookupExplicitNamespace(schemaname, 
missing_ok);
                        if (OidIsValid(namespaceId))
+                       {
                                typoid = GetSysCacheOid2(TYPENAMENSP, 
Anum_pg_type_oid,
                                                                                
 PointerGetDatum(typname),
                                                                                
 ObjectIdGetDatum(namespaceId));
+
+                               /*
+                                * Treat an untrusted match as nonexistent, as
+                                * TypenameGetTypidExtended does for 
unqualified names.
+                                */
+                               if (OidIsValid(typoid) && creating_extension &&
+                                       !TypeIsTrustedInExtensionScript(typoid))
+                                       typoid = InvalidOid;
+                       }
                        else
                                typoid = InvalidOid;
 
@@ -216,6 +227,22 @@ LookupTypeNameExtended(ParseState *pstate,
        return (Type) tup;
 }
 
+/*
+ * errdetail_untrusted_type for a TypeName; %TYPE references name a column,
+ * not a type, so they get no detail.
+ */
+int
+errdetail_untrusted_typename(const TypeName *typeName)
+{
+       char       *schemaname;
+       char       *typname;
+
+       if (typeName->pct_type || !creating_extension)
+               return 0;
+       DeconstructQualifiedName(typeName->names, &schemaname, &typname);
+       return errdetail_untrusted_type(schemaname, typname);
+}
+
 /*
  * LookupTypeNameOid
  *             Given a TypeName object, lookup the pg_type syscache entry of 
the type.
@@ -242,6 +269,7 @@ LookupTypeNameOid(ParseState *pstate, const TypeName 
*typeName, bool missing_ok)
                                        (errcode(ERRCODE_UNDEFINED_OBJECT),
                                         errmsg("type \"%s\" does not exist",
                                                        
TypeNameToString(typeName)),
+                                        errdetail_untrusted_typename(typeName),
                                         parser_errposition(pstate, 
typeName->location)));
 
                return InvalidOid;
@@ -271,6 +299,7 @@ typenameType(ParseState *pstate, const TypeName *typeName, 
int32 *typmod_p)
                                (errcode(ERRCODE_UNDEFINED_OBJECT),
                                 errmsg("type \"%s\" does not exist",
                                                TypeNameToString(typeName)),
+                                errdetail_untrusted_typename(typeName),
                                 parser_errposition(pstate, 
typeName->location)));
        if (!((Form_pg_type) GETSTRUCT(tup))->typisdefined)
                ereport(ERROR,
@@ -799,7 +828,8 @@ parseTypeString(const char *str, Oid *typeid_p, int32 
*typmod_p,
                ereturn(escontext, false,
                                (errcode(ERRCODE_UNDEFINED_OBJECT),
                                 errmsg("type \"%s\" does not exist",
-                                               TypeNameToString(typeName))));
+                                               TypeNameToString(typeName)),
+                                errdetail_untrusted_typename(typeName)));
        }
        else
        {
diff --git a/src/backend/utils/cache/plancache.c 
b/src/backend/utils/cache/plancache.c
index a1b406cee29..a7322db2dc9 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -61,6 +61,7 @@
 
 #include "access/transam.h"
 #include "catalog/namespace.h"
+#include "commands/extension.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
@@ -243,6 +244,7 @@ CreateCachedPlan(const RawStmt *raw_parse_tree,
        plansource->rewriteRoleId = InvalidOid;
        plansource->rewriteRowSecurity = false;
        plansource->dependsOnRLS = false;
+       plansource->parsedInExtensionScript = false;
        plansource->gplan = NULL;
        plansource->is_oneshot = false;
        plansource->is_complete = false;
@@ -342,6 +344,7 @@ CreateOneShotCachedPlan(RawStmt *raw_parse_tree,
        plansource->rewriteRoleId = InvalidOid;
        plansource->rewriteRowSecurity = false;
        plansource->dependsOnRLS = false;
+       plansource->parsedInExtensionScript = false;
        plansource->gplan = NULL;
        plansource->is_oneshot = true;
        plansource->is_complete = false;
@@ -462,6 +465,9 @@ CompleteCachedPlan(CachedPlanSource *plansource,
                plansource->rewriteRoleId = GetUserId();
                plansource->rewriteRowSecurity = row_security;
 
+               /* Remember whether an extension script was running. */
+               plansource->parsedInExtensionScript = creating_extension;
+
                /*
                 * Also save the current search_path in the query_context.  
(This
                 * should not generate much extra cruft either, since almost 
certainly
@@ -733,6 +739,20 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
                }
        }
 
+       /*
+        * Name resolution applies extra trust checks while an extension script
+        * runs, so a tree analyzed outside one must not be reused inside it or
+        * vice versa.  The search_path check above need not have caught this.
+        */
+       if (plansource->is_valid &&
+               plansource->parsedInExtensionScript != creating_extension)
+       {
+               /* Invalidate the querytree and generic plan */
+               plansource->is_valid = false;
+               if (plansource->gplan)
+                       plansource->gplan->is_valid = false;
+       }
+
        /*
         * If the query rewrite phase had a possible RLS dependency, we must 
redo
         * it if either the role or the row_security setting has changed.
@@ -918,6 +938,9 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
        plansource->rewriteRoleId = GetUserId();
        plansource->rewriteRowSecurity = row_security;
 
+       /* Remember whether an extension script was running. */
+       plansource->parsedInExtensionScript = creating_extension;
+
        /*
         * Also save the current search_path in the query_context.  (This should
         * not generate much extra cruft either, since almost certainly the path
@@ -1498,6 +1521,7 @@ CachedPlanAllowsSimpleValidityCheck(CachedPlanSource 
*plansource,
        Assert(plan == plansource->gplan);
        Assert(plansource->search_path != NULL);
        Assert(SearchPathMatchesCurrentEnvironment(plansource->search_path));
+       Assert(plansource->parsedInExtensionScript == creating_extension);
 
        /* We don't support oneshot plans here. */
        if (plansource->is_oneshot)
@@ -1623,6 +1647,10 @@ CachedPlanIsSimplyValid(CachedPlanSource *plansource, 
CachedPlan *plan,
        if (!SearchPathMatchesCurrentEnvironment(plansource->search_path))
                return false;
 
+       /* Are we in the same extension-script context as when we made it? */
+       if (plansource->parsedInExtensionScript != creating_extension)
+               return false;
+
        /* It's still good.  Bump refcount if requested. */
        if (owner)
        {
@@ -1743,6 +1771,7 @@ CopyCachedPlan(CachedPlanSource *plansource)
        newsource->rewriteRoleId = plansource->rewriteRoleId;
        newsource->rewriteRowSecurity = plansource->rewriteRowSecurity;
        newsource->dependsOnRLS = plansource->dependsOnRLS;
+       newsource->parsedInExtensionScript = 
plansource->parsedInExtensionScript;
 
        newsource->gplan = NULL;
 
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 9453a3e4932..d9e5f53c9eb 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -56,6 +56,8 @@ typedef struct _FuncCandidateList
 #define FGC_ARGNAMES_VALID     0x0100  /* Found a fully-valid use of argnames 
*/
 /* These bits are actually filled by func_get_detail: */
 #define FGC_VARIADIC_FAIL      0x0200  /* Disallowed VARIADIC with named args 
*/
+/* This bit is set only while an extension script is running: */
+#define FGC_UNTRUSTED_SKIP     0x0400  /* Ignored an untrusted candidate */
 
 /*
  * Result of checkTempNamespaceStatus
@@ -122,6 +124,12 @@ extern FuncCandidateList FuncnameGetCandidates(List *names,
                                                                                
           bool include_out_arguments,
                                                                                
           bool missing_ok,
                                                                                
           int *fgc_flags);
+extern bool ObjectIsTrustedInExtensionScript(Oid classId, Oid objectId,
+                                                                               
         Oid namespaceId, Oid ownerId);
+extern bool TypeIsTrustedInExtensionScript(Oid typid);
+extern int     errdetail_untrusted_relation(const RangeVar *relation);
+extern int     errdetail_untrusted_type(const char *schemaname,
+                                                                        const 
char *typname);
 extern bool FunctionIsVisible(Oid funcid);
 
 extern Oid     OpernameGetOprid(List *names, Oid oprleft, Oid oprright);
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 8eaec2d4f68..327502f9311 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -34,6 +34,7 @@ extern PGDLLIMPORT Oid CurrentExtensionObject;
 
 extern void GetExtensionCreationState(bool *creating, Oid *extensionObject);
 extern void SetExtensionCreationState(bool creating, Oid extensionObject);
+extern bool CurrentExtensionRequires(Oid extensionId);
 
 
 extern ObjectAddress CreateExtension(ParseState *pstate, CreateExtensionStmt 
*stmt);
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 79f10b5b5b2..f81a81ec6ba 100644
--- a/src/include/parser/parse_type.h
+++ b/src/include/parser/parse_type.h
@@ -29,6 +29,7 @@ extern Oid    LookupTypeNameOid(ParseState *pstate, const 
TypeName *typeName,
 extern Type typenameType(ParseState *pstate, const TypeName *typeName,
                                                 int32 *typmod_p);
 extern Oid     typenameTypeId(ParseState *pstate, const TypeName *typeName);
+extern int     errdetail_untrusted_typename(const TypeName *typeName);
 extern void typenameTypeIdAndMod(ParseState *pstate, const TypeName *typeName,
                                                                 Oid *typeid_p, 
int32 *typmod_p);
 
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index a0355e79c28..042ea128866 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -129,6 +129,7 @@ typedef struct CachedPlanSource
        Oid                     rewriteRoleId;  /* Role ID we did rewriting for 
*/
        bool            rewriteRowSecurity; /* row_security used during rewrite 
*/
        bool            dependsOnRLS;   /* is rewritten query specific to the 
above? */
+       bool            parsedInExtensionScript;        /* creating_extension 
at parse */
        /* If we have a generic plan, this is a reference-counted link to it: */
        struct CachedPlan *gplan;       /* generic plan, or NULL if not valid */
        /* Some state flags: */
diff --git a/src/test/modules/test_extensions/Makefile 
b/src/test/modules/test_extensions/Makefile
index d1b0b81e5fd..5fe678f4be0 100644
--- a/src/test/modules/test_extensions/Makefile
+++ b/src/test/modules/test_extensions/Makefile
@@ -9,6 +9,10 @@ EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 
test_ext6 \
             test_ext_cyclic1 test_ext_cyclic2 \
             test_ext_extschema \
             test_ext_evttrig \
+            test_ext_overload test_ext_overload_strict \
+            test_ext_overload_nosuper \
+            test_ext_overload_parallel \
+            test_ext_overload_req test_ext_overload_req_dep \
             test_ext_set_schema \
             test_ext_req_schema1 test_ext_req_schema2 test_ext_req_schema3
 
@@ -25,6 +29,22 @@ DATA = test_ext1--1.0.sql test_ext2--1.0.sql 
test_ext3--1.0.sql \
        test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql \
        test_ext_extschema--1.0.sql \
        test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql \
+       test_ext_overload--1.0.sql \
+       test_ext_overload_strict--1.0.sql \
+       test_ext_overload_strict--2.0.sql \
+       test_ext_overload_strict--3.0.sql \
+       test_ext_overload_strict--4.0.sql \
+       test_ext_overload_strict--5.0.sql \
+       test_ext_overload_strict--6.0.sql \
+       test_ext_overload_strict--7.0.sql \
+       test_ext_overload_strict--8.0.sql \
+       test_ext_overload_strict--9.0.sql \
+       test_ext_overload_strict--10.0.sql \
+       test_ext_overload_nosuper--1.0.sql \
+       test_ext_overload_nosuper--1.0--2.0.sql \
+       test_ext_overload_parallel--1.0.sql \
+       test_ext_overload_req--1.0.sql \
+       test_ext_overload_req_dep--1.0.sql \
        test_ext_set_schema--1.0.sql \
        test_ext_req_schema1--1.0.sql \
        test_ext_req_schema2--1.0.sql \
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out 
b/src/test/modules/test_extensions/expected/test_extensions.out
index 1b5debdeeb1..392de3598b7 100644
--- a/src/test/modules/test_extensions/expected/test_extensions.out
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -667,3 +667,351 @@ SELECT test_s_dep.dep_req2();
 
 DROP EXTENSION test_ext_req_schema1 CASCADE;
 NOTICE:  drop cascades to extension test_ext_req_schema2
+-- Verify that name resolution during an extension script cannot be captured
+-- by objects an unprivileged user planted in the extension's schema.
+CREATE ROLE regress_ext_user;
+CREATE SCHEMA test_overload;
+GRANT CREATE, USAGE ON SCHEMA test_overload TO regress_ext_user;
+-- As the unprivileged user, plant differently-typed siblings and the sole
+-- definition of helper_only().
+SET ROLE regress_ext_user;
+CREATE FUNCTION test_overload.f(text) RETURNS text
+    AS $$ SELECT 'attacker'::text $$ LANGUAGE sql IMMUTABLE;
+CREATE FUNCTION test_overload.opimpl_bad(text, text) RETURNS text
+    AS $$ SELECT 'attacker'::text $$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR test_overload.### (leftarg = text, rightarg = text,
+                                   function = test_overload.opimpl_bad);
+CREATE FUNCTION test_overload.helper_only(text) RETURNS text
+    AS $$ SELECT 'attacker'::text $$ LANGUAGE sql IMMUTABLE;
+CREATE FUNCTION test_overload.opimpl_only(text, text) RETURNS text
+    AS $$ SELECT 'attacker'::text $$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR test_overload.@@@ (leftarg = text, rightarg = text,
+                                   function = test_overload.opimpl_only);
+CREATE FUNCTION test_overload.opimpl_vc(varchar, varchar) RETURNS text
+    AS $$ SELECT 'attacker'::text $$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR test_overload.<<< (leftarg = varchar, rightarg = varchar,
+                                   function = test_overload.opimpl_vc);
+-- A planted table and domain, for the relation and type lookups.
+CREATE TABLE test_overload.cfg (who text);
+CREATE DOMAIN test_overload.planted_dom AS text;
+RESET ROLE;
+-- A schema outside the script's search path, holding a planted operator.
+CREATE SCHEMA test_overload_other;
+GRANT CREATE, USAGE ON SCHEMA test_overload_other TO regress_ext_user;
+SET ROLE regress_ext_user;
+CREATE OPERATOR test_overload_other.&&& (leftarg = text, rightarg = text,
+                                         function = test_overload.opimpl_only);
+RESET ROLE;
+-- Installing the extension resolves f('abc') and the ### operator to the
+-- extension's own (trusted) objects, not the planted ones.
+CREATE EXTENSION test_ext_overload SCHEMA test_overload;
+SELECT fn, op FROM test_overload.captured;
+    fn     |    op     
+-----------+-----------
+ extension | extension
+(1 row)
+
+-- Outside of extension scripts, ordinary resolution rules are unchanged: the
+-- same calls reach the planted objects.
+SELECT test_overload.f('abc') AS fn,
+       ('a' OPERATOR(test_overload.###) 'b') AS op;
+    fn    |    op    
+----------+----------
+ attacker | attacker
+(1 row)
+
+-- When only an untrusted candidate exists, the script refuses to call it.
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload;  -- fails
+ERROR:  function test_overload.helper_only(unknown) does not exist
+LINE 2:     SELECT test_overload.helper_only('abc') AS r
+                   ^
+DETAIL:  A function of that name exists, but it is not trusted while an 
extension script runs.
+HINT:  Only objects in pg_catalog, owned by a superuser, or belonging to the 
extension or one it requires are trusted.
+QUERY:  CREATE TABLE test_overload.captured AS
+    SELECT test_overload.helper_only('abc') AS r
+CONTEXT:  extension script file "test_ext_overload_strict--1.0.sql", near line 
8
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '2.0';  
-- fails
+ERROR:  operator does not exist: unknown test_overload.@@@ unknown
+LINE 2:     SELECT ('a' OPERATOR(test_overload.@@@) 'b') AS r
+                        ^
+DETAIL:  An operator of that name exists, but it is not trusted while an 
extension script runs.
+HINT:  Only objects in pg_catalog, owned by a superuser, or belonging to the 
extension or one it requires are trusted.
+QUERY:  CREATE TABLE test_overload.captured AS
+    SELECT ('a' OPERATOR(test_overload.@@@) 'b') AS r
+CONTEXT:  extension script file "test_ext_overload_strict--2.0.sql", near line 
8
+-- A planted operator named as COMMUTATOR is refused as well.
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '3.0';  
-- fails
+ERROR:  operator does not exist: character varying <<< character varying
+DETAIL:  An operator of that name exists, but it is not trusted while an 
extension script runs.
+HINT:  Only objects in pg_catalog, owned by a superuser, or belonging to the 
extension or one it requires are trusted.
+CONTEXT:  SQL statement "CREATE OPERATOR test_overload.>>> (leftarg = varchar, 
rightarg = varchar,
+                                 function = test_overload.opimpl,
+                                 commutator = <<<)"
+extension script file "test_ext_overload_strict--3.0.sql", near line 8
+-- Trusted candidate visible but arguments don't match: argument error, with
+-- the untrusted candidate as a hint.
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '4.0';  
-- fails
+ERROR:  function test_overload.f(integer) does not exist
+LINE 2:     SELECT test_overload.f(1) AS r
+                   ^
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  A function of that name was ignored because it is not trusted while an 
extension script runs.
+QUERY:  CREATE TABLE test_overload.captured AS
+    SELECT test_overload.f(1) AS r
+CONTEXT:  extension script file "test_ext_overload_strict--4.0.sql", near line 
8
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '5.0';  
-- fails
+ERROR:  operator does not exist: integer test_overload.### integer
+LINE 2:     SELECT (1 OPERATOR(test_overload.###) 2) AS r
+                      ^
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  An operator of that name was ignored because it is not trusted while an 
extension script runs.
+QUERY:  CREATE TABLE test_overload.captured AS
+    SELECT (1 OPERATOR(test_overload.###) 2) AS r
+CONTEXT:  extension script file "test_ext_overload_strict--5.0.sql", near line 
7
+-- Untrusted candidate outside the search path: ordinary not-in-path error.
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '6.0';  
-- fails
+ERROR:  operator does not exist: unknown &&& unknown
+LINE 2:     SELECT ('a' &&& 'b') AS r
+                        ^
+DETAIL:  An operator of that name exists, but it is not in the search_path.
+QUERY:  CREATE TABLE test_overload.captured AS
+    SELECT ('a' &&& 'b') AS r
+CONTEXT:  extension script file "test_ext_overload_strict--6.0.sql", near line 
7
+-- A planted relation or type is refused whether the script qualifies the
+-- name with the schema or leaves it to search_path.
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '7.0';  
-- fails
+ERROR:  relation "test_overload.cfg" does not exist
+LINE 1: INSERT INTO test_overload.cfg VALUES ('extension')
+                    ^
+DETAIL:  A relation of that name exists, but it is not trusted while an 
extension script runs.
+HINT:  Only objects in pg_catalog, owned by a superuser, or belonging to the 
extension or one it requires are trusted.
+QUERY:  INSERT INTO test_overload.cfg VALUES ('extension')
+CONTEXT:  extension script file "test_ext_overload_strict--7.0.sql", near line 
12
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '8.0';  
-- fails
+ERROR:  type test_overload.planted_dom does not exist
+LINE 1: CREATE FUNCTION test_overload.g(test_overload.planted_dom) R...
+                                        ^
+DETAIL:  A type of that name exists, but it is not trusted while an extension 
script runs.
+HINT:  Only objects in pg_catalog, owned by a superuser, or belonging to the 
extension or one it requires are trusted.
+QUERY:  CREATE FUNCTION test_overload.g(test_overload.planted_dom) RETURNS text
+    AS $$ SELECT 'extension'::text $$ LANGUAGE sql IMMUTABLE
+CONTEXT:  extension script file "test_ext_overload_strict--8.0.sql", near line 
7
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '9.0';  
-- fails
+ERROR:  relation "cfg" does not exist
+LINE 1: INSERT INTO cfg VALUES ('extension')
+                    ^
+DETAIL:  A relation of that name exists, but it is not trusted while an 
extension script runs.
+HINT:  Only objects in pg_catalog, owned by a superuser, or belonging to the 
extension or one it requires are trusted.
+QUERY:  INSERT INTO cfg VALUES ('extension')
+CONTEXT:  extension script file "test_ext_overload_strict--9.0.sql", near line 
7
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '10.0'; 
 -- fails
+ERROR:  type planted_dom does not exist
+LINE 1: CREATE FUNCTION test_overload.g(planted_dom) RETURNS text
+                                        ^
+DETAIL:  A type of that name exists, but it is not trusted while an extension 
script runs.
+HINT:  Only objects in pg_catalog, owned by a superuser, or belonging to the 
extension or one it requires are trusted.
+QUERY:  CREATE FUNCTION test_overload.g(planted_dom) RETURNS text
+    AS $$ SELECT 'extension'::text $$ LANGUAGE sql IMMUTABLE
+CONTEXT:  extension script file "test_ext_overload_strict--10.0.sql", near 
line 8
+-- The plants are untouched.
+SELECT count(*) AS rows_in_plant FROM test_overload.cfg;
+ rows_in_plant 
+---------------
+             0
+(1 row)
+
+DROP EXTENSION test_ext_overload;
+DROP SCHEMA test_overload CASCADE;
+NOTICE:  drop cascades to 11 other objects
+DETAIL:  drop cascades to function test_overload.f(text)
+drop cascades to function test_overload.opimpl_bad(text,text)
+drop cascades to operator test_overload.###(text,text)
+drop cascades to function test_overload.helper_only(text)
+drop cascades to function test_overload.opimpl_only(text,text)
+drop cascades to operator test_overload_other.&&&(text,text)
+drop cascades to operator test_overload.@@@(text,text)
+drop cascades to function test_overload.opimpl_vc(character varying,character 
varying)
+drop cascades to operator test_overload.<<<(character varying,character 
varying)
+drop cascades to table test_overload.cfg
+drop cascades to type test_overload.planted_dom
+DROP SCHEMA test_overload_other CASCADE;
+DROP ROLE regress_ext_user;
+-- A "superuser = false" script runs as the invoking user, so its objects are
+-- owned by that role.  They must still be trusted, and another user's plant
+-- must not be.
+CREATE ROLE regress_ext_owner;
+CREATE ROLE regress_ext_attacker;
+DO $$ BEGIN
+    EXECUTE format('GRANT CREATE ON DATABASE %I TO regress_ext_owner',
+                   current_database());
+END $$;
+CREATE SCHEMA test_nosuper AUTHORIZATION regress_ext_owner;
+GRANT CREATE, USAGE ON SCHEMA test_nosuper TO regress_ext_attacker;
+-- Attacker plants a preferred-type (text) sibling of the extension's g().
+SET ROLE regress_ext_attacker;
+CREATE FUNCTION test_nosuper.g(text) RETURNS text
+    AS $$ SELECT 'attacker'::text $$ LANGUAGE sql IMMUTABLE;
+RESET ROLE;
+-- The script runs as regress_ext_owner and must resolve g('abc') to its own
+-- varchar function, not the attacker's text one.
+SET ROLE regress_ext_owner;
+CREATE EXTENSION test_ext_overload_nosuper SCHEMA test_nosuper;
+SELECT fn FROM test_nosuper.captured_nosuper;
+    fn     
+-----------
+ extension
+(1 row)
+
+RESET ROLE;
+-- An update run by another role (here the superuser) must still reach the
+-- extension's own g(), which the install left owned by regress_ext_owner.
+ALTER EXTENSION test_ext_overload_nosuper UPDATE TO '2.0';
+SELECT fn FROM test_nosuper.updated;
+    fn     
+-----------
+ extension
+(1 row)
+
+DROP EXTENSION test_ext_overload_nosuper;
+DROP SCHEMA test_nosuper CASCADE;
+NOTICE:  drop cascades to function test_nosuper.g(text)
+DO $$ BEGIN
+    EXECUTE format('REVOKE CREATE ON DATABASE %I FROM regress_ext_owner',
+                   current_database());
+END $$;
+DROP ROLE regress_ext_owner;
+DROP ROLE regress_ext_attacker;
+-- Resolution in a parallel worker must apply the same check, so
+-- creating_extension must reach the worker.  Force wrap() into a worker with
+-- debug_parallel_query.
+CREATE ROLE regress_ext_attacker NOSUPERUSER;
+CREATE SCHEMA test_parallel;
+GRANT CREATE, USAGE ON SCHEMA test_parallel TO regress_ext_attacker;
+SET ROLE regress_ext_attacker;
+CREATE FUNCTION test_parallel.probe(text) RETURNS text
+    AS $$ SELECT 'attacker'::text $$ LANGUAGE sql IMMUTABLE PARALLEL SAFE;
+RESET ROLE;
+SET debug_parallel_query = on;
+CREATE EXTENSION test_ext_overload_parallel SCHEMA test_parallel;
+RESET debug_parallel_query;
+-- The worker resolved probe('x') to the extension's own probe(varchar), not
+-- the planted probe(text).
+SELECT who FROM test_parallel.captured;
+    who    
+-----------
+ extension
+(1 row)
+
+DROP EXTENSION test_ext_overload_parallel;
+DROP SCHEMA test_parallel CASCADE;
+NOTICE:  drop cascades to function test_parallel.probe(text)
+DROP ROLE regress_ext_attacker;
+-- A plant in the extension's own schema must not shadow a required
+-- extension's object, for any kind of reference: call, DDL by name, type,
+-- relation, or a resolution cached before the script.
+CREATE ROLE regress_ext_attacker;
+CREATE ROLE regress_ext_reqowner;
+DO $$ BEGIN
+    EXECUTE format('GRANT CREATE ON DATABASE %I TO regress_ext_reqowner',
+                   current_database());
+END $$;
+CREATE SCHEMA test_reqdep AUTHORIZATION regress_ext_reqowner;
+CREATE SCHEMA test_req;
+GRANT CREATE, USAGE ON SCHEMA test_req TO regress_ext_attacker;
+-- The required extension is "superuser = false" and installed by an ordinary
+-- role, so its objects are reachable only through required-extension
+-- membership.
+SET ROLE regress_ext_reqowner;
+CREATE EXTENSION test_ext_overload_req_dep SCHEMA test_reqdep;
+RESET ROLE;
+SET ROLE regress_ext_attacker;
+CREATE FUNCTION test_req.reqcall(int) RETURNS text
+    AS $$ SELECT 'attacker'::text $$ LANGUAGE sql IMMUTABLE;
+CREATE FUNCTION test_req.reqeq(int, int) RETURNS boolean
+    AS $$ SELECT false $$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR test_req.=== (leftarg = integer, rightarg = integer,
+                              function = test_req.reqeq);
+-- Resolving to this domain would run pwn() with the script's privileges.
+CREATE FUNCTION test_req.pwn(text) RETURNS boolean
+    AS $$ BEGIN RAISE EXCEPTION 'attacker code executed'; END $$ LANGUAGE 
plpgsql;
+CREATE DOMAIN test_req.reqdom AS text CHECK (test_req.pwn(VALUE));
+CREATE TABLE test_req.reqtab(t text);
+RESET ROLE;
+-- Cache a resolution made outside any script, under the search_path the
+-- script will pin; that plan must not be reused inside the script.
+SET search_path = test_req, test_reqdep, pg_temp;
+SELECT test_reqdep.reqplpgsql() AS warmed_outside_script;
+ warmed_outside_script 
+-----------------------
+ attacker
+(1 row)
+
+RESET search_path;
+CREATE EXTENSION test_ext_overload_req SCHEMA test_req;
+-- Every reference resolved to the required extension's objects (in
+-- test_reqdep), not the planted ones in test_req.
+SELECT c.who, c.who_cached, n.nspname AS domain_schema
+  FROM test_req.captured c
+  JOIN pg_type t ON t.oid = c.dom
+  JOIN pg_namespace n ON n.oid = t.typnamespace;
+   who    | who_cached | domain_schema 
+----------+------------+---------------
+ required | required   | test_reqdep
+(1 row)
+
+SELECT n.nspname AS operator_func_schema
+  FROM pg_operator o
+  JOIN pg_proc p ON p.oid = o.oprcode
+  JOIN pg_namespace n ON n.oid = p.pronamespace
+ WHERE o.oprname = '###' AND o.oprnamespace = 'test_req'::regnamespace;
+ operator_func_schema 
+----------------------
+ test_reqdep
+(1 row)
+
+SELECT 'test_reqdep.reqtab' AS tbl, count(*) FROM test_reqdep.reqtab
+UNION ALL
+SELECT 'test_req.reqtab', count(*) FROM test_req.reqtab;
+        tbl         | count 
+--------------------+-------
+ test_reqdep.reqtab |     1
+ test_req.reqtab    |     0
+(2 rows)
+
+SELECT n.nspname AS opfamily_member_schema
+  FROM pg_amop a
+  JOIN pg_opfamily f ON f.oid = a.amopfamily
+  JOIN pg_operator o ON o.oid = a.amopopr
+  JOIN pg_namespace n ON n.oid = o.oprnamespace
+ WHERE f.opfname = 'reqfam';
+ opfamily_member_schema 
+------------------------
+ test_reqdep
+(1 row)
+
+-- Outside the script the cached plan is good again.
+SET search_path = test_req, test_reqdep, pg_temp;
+SELECT test_reqdep.reqplpgsql() AS after_script;
+ after_script 
+--------------
+ attacker
+(1 row)
+
+RESET search_path;
+DROP EXTENSION test_ext_overload_req;
+DROP EXTENSION test_ext_overload_req_dep;
+DROP SCHEMA test_req CASCADE;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to function test_req.reqcall(integer)
+drop cascades to function test_req.reqeq(integer,integer)
+drop cascades to operator test_req.===(integer,integer)
+drop cascades to function test_req.pwn(text)
+drop cascades to type test_req.reqdom
+drop cascades to table test_req.reqtab
+DROP SCHEMA test_reqdep CASCADE;
+DO $$ BEGIN
+    EXECUTE format('REVOKE CREATE ON DATABASE %I FROM regress_ext_reqowner',
+                   current_database());
+END $$;
+DROP ROLE regress_ext_attacker;
+DROP ROLE regress_ext_reqowner;
diff --git a/src/test/modules/test_extensions/meson.build 
b/src/test/modules/test_extensions/meson.build
index 2c7cea189e2..13530a0530a 100644
--- a/src/test/modules/test_extensions/meson.build
+++ b/src/test/modules/test_extensions/meson.build
@@ -36,6 +36,28 @@ test_install_data += files(
   'test_ext_evttrig--1.0--2.0.sql',
   'test_ext_evttrig--1.0.sql',
   'test_ext_evttrig.control',
+  'test_ext_overload--1.0.sql',
+  'test_ext_overload.control',
+  'test_ext_overload_strict--1.0.sql',
+  'test_ext_overload_strict--2.0.sql',
+  'test_ext_overload_strict--3.0.sql',
+  'test_ext_overload_strict--4.0.sql',
+  'test_ext_overload_strict--5.0.sql',
+  'test_ext_overload_strict--6.0.sql',
+  'test_ext_overload_strict--7.0.sql',
+  'test_ext_overload_strict--8.0.sql',
+  'test_ext_overload_strict--9.0.sql',
+  'test_ext_overload_strict--10.0.sql',
+  'test_ext_overload_strict.control',
+  'test_ext_overload_nosuper--1.0--2.0.sql',
+  'test_ext_overload_nosuper--1.0.sql',
+  'test_ext_overload_nosuper.control',
+  'test_ext_overload_parallel--1.0.sql',
+  'test_ext_overload_parallel.control',
+  'test_ext_overload_req--1.0.sql',
+  'test_ext_overload_req.control',
+  'test_ext_overload_req_dep--1.0.sql',
+  'test_ext_overload_req_dep.control',
   'test_ext_req_schema1--1.0.sql',
   'test_ext_req_schema1.control',
   'test_ext_req_schema2--1.0.sql',
diff --git a/src/test/modules/test_extensions/sql/test_extensions.sql 
b/src/test/modules/test_extensions/sql/test_extensions.sql
index b5878f6f80f..7d00317b18a 100644
--- a/src/test/modules/test_extensions/sql/test_extensions.sql
+++ b/src/test/modules/test_extensions/sql/test_extensions.sql
@@ -303,3 +303,197 @@ ALTER EXTENSION test_ext_req_schema1 SET SCHEMA 
test_s_dep2;  -- now ok
 SELECT test_s_dep2.dep_req1();
 SELECT test_s_dep.dep_req2();
 DROP EXTENSION test_ext_req_schema1 CASCADE;
+
+-- Verify that name resolution during an extension script cannot be captured
+-- by objects an unprivileged user planted in the extension's schema.
+CREATE ROLE regress_ext_user;
+CREATE SCHEMA test_overload;
+GRANT CREATE, USAGE ON SCHEMA test_overload TO regress_ext_user;
+-- As the unprivileged user, plant differently-typed siblings and the sole
+-- definition of helper_only().
+SET ROLE regress_ext_user;
+CREATE FUNCTION test_overload.f(text) RETURNS text
+    AS $$ SELECT 'attacker'::text $$ LANGUAGE sql IMMUTABLE;
+CREATE FUNCTION test_overload.opimpl_bad(text, text) RETURNS text
+    AS $$ SELECT 'attacker'::text $$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR test_overload.### (leftarg = text, rightarg = text,
+                                   function = test_overload.opimpl_bad);
+CREATE FUNCTION test_overload.helper_only(text) RETURNS text
+    AS $$ SELECT 'attacker'::text $$ LANGUAGE sql IMMUTABLE;
+CREATE FUNCTION test_overload.opimpl_only(text, text) RETURNS text
+    AS $$ SELECT 'attacker'::text $$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR test_overload.@@@ (leftarg = text, rightarg = text,
+                                   function = test_overload.opimpl_only);
+CREATE FUNCTION test_overload.opimpl_vc(varchar, varchar) RETURNS text
+    AS $$ SELECT 'attacker'::text $$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR test_overload.<<< (leftarg = varchar, rightarg = varchar,
+                                   function = test_overload.opimpl_vc);
+-- A planted table and domain, for the relation and type lookups.
+CREATE TABLE test_overload.cfg (who text);
+CREATE DOMAIN test_overload.planted_dom AS text;
+RESET ROLE;
+-- A schema outside the script's search path, holding a planted operator.
+CREATE SCHEMA test_overload_other;
+GRANT CREATE, USAGE ON SCHEMA test_overload_other TO regress_ext_user;
+SET ROLE regress_ext_user;
+CREATE OPERATOR test_overload_other.&&& (leftarg = text, rightarg = text,
+                                         function = test_overload.opimpl_only);
+RESET ROLE;
+-- Installing the extension resolves f('abc') and the ### operator to the
+-- extension's own (trusted) objects, not the planted ones.
+CREATE EXTENSION test_ext_overload SCHEMA test_overload;
+SELECT fn, op FROM test_overload.captured;
+-- Outside of extension scripts, ordinary resolution rules are unchanged: the
+-- same calls reach the planted objects.
+SELECT test_overload.f('abc') AS fn,
+       ('a' OPERATOR(test_overload.###) 'b') AS op;
+-- When only an untrusted candidate exists, the script refuses to call it.
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload;  -- fails
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '2.0';  
-- fails
+-- A planted operator named as COMMUTATOR is refused as well.
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '3.0';  
-- fails
+-- Trusted candidate visible but arguments don't match: argument error, with
+-- the untrusted candidate as a hint.
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '4.0';  
-- fails
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '5.0';  
-- fails
+-- Untrusted candidate outside the search path: ordinary not-in-path error.
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '6.0';  
-- fails
+-- A planted relation or type is refused whether the script qualifies the
+-- name with the schema or leaves it to search_path.
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '7.0';  
-- fails
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '8.0';  
-- fails
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '9.0';  
-- fails
+CREATE EXTENSION test_ext_overload_strict SCHEMA test_overload VERSION '10.0'; 
 -- fails
+-- The plants are untouched.
+SELECT count(*) AS rows_in_plant FROM test_overload.cfg;
+DROP EXTENSION test_ext_overload;
+DROP SCHEMA test_overload CASCADE;
+DROP SCHEMA test_overload_other CASCADE;
+DROP ROLE regress_ext_user;
+
+-- A "superuser = false" script runs as the invoking user, so its objects are
+-- owned by that role.  They must still be trusted, and another user's plant
+-- must not be.
+CREATE ROLE regress_ext_owner;
+CREATE ROLE regress_ext_attacker;
+DO $$ BEGIN
+    EXECUTE format('GRANT CREATE ON DATABASE %I TO regress_ext_owner',
+                   current_database());
+END $$;
+CREATE SCHEMA test_nosuper AUTHORIZATION regress_ext_owner;
+GRANT CREATE, USAGE ON SCHEMA test_nosuper TO regress_ext_attacker;
+-- Attacker plants a preferred-type (text) sibling of the extension's g().
+SET ROLE regress_ext_attacker;
+CREATE FUNCTION test_nosuper.g(text) RETURNS text
+    AS $$ SELECT 'attacker'::text $$ LANGUAGE sql IMMUTABLE;
+RESET ROLE;
+-- The script runs as regress_ext_owner and must resolve g('abc') to its own
+-- varchar function, not the attacker's text one.
+SET ROLE regress_ext_owner;
+CREATE EXTENSION test_ext_overload_nosuper SCHEMA test_nosuper;
+SELECT fn FROM test_nosuper.captured_nosuper;
+RESET ROLE;
+-- An update run by another role (here the superuser) must still reach the
+-- extension's own g(), which the install left owned by regress_ext_owner.
+ALTER EXTENSION test_ext_overload_nosuper UPDATE TO '2.0';
+SELECT fn FROM test_nosuper.updated;
+DROP EXTENSION test_ext_overload_nosuper;
+DROP SCHEMA test_nosuper CASCADE;
+DO $$ BEGIN
+    EXECUTE format('REVOKE CREATE ON DATABASE %I FROM regress_ext_owner',
+                   current_database());
+END $$;
+DROP ROLE regress_ext_owner;
+DROP ROLE regress_ext_attacker;
+
+-- Resolution in a parallel worker must apply the same check, so
+-- creating_extension must reach the worker.  Force wrap() into a worker with
+-- debug_parallel_query.
+CREATE ROLE regress_ext_attacker NOSUPERUSER;
+CREATE SCHEMA test_parallel;
+GRANT CREATE, USAGE ON SCHEMA test_parallel TO regress_ext_attacker;
+SET ROLE regress_ext_attacker;
+CREATE FUNCTION test_parallel.probe(text) RETURNS text
+    AS $$ SELECT 'attacker'::text $$ LANGUAGE sql IMMUTABLE PARALLEL SAFE;
+RESET ROLE;
+SET debug_parallel_query = on;
+CREATE EXTENSION test_ext_overload_parallel SCHEMA test_parallel;
+RESET debug_parallel_query;
+-- The worker resolved probe('x') to the extension's own probe(varchar), not
+-- the planted probe(text).
+SELECT who FROM test_parallel.captured;
+DROP EXTENSION test_ext_overload_parallel;
+DROP SCHEMA test_parallel CASCADE;
+DROP ROLE regress_ext_attacker;
+
+-- A plant in the extension's own schema must not shadow a required
+-- extension's object, for any kind of reference: call, DDL by name, type,
+-- relation, or a resolution cached before the script.
+CREATE ROLE regress_ext_attacker;
+CREATE ROLE regress_ext_reqowner;
+DO $$ BEGIN
+    EXECUTE format('GRANT CREATE ON DATABASE %I TO regress_ext_reqowner',
+                   current_database());
+END $$;
+CREATE SCHEMA test_reqdep AUTHORIZATION regress_ext_reqowner;
+CREATE SCHEMA test_req;
+GRANT CREATE, USAGE ON SCHEMA test_req TO regress_ext_attacker;
+-- The required extension is "superuser = false" and installed by an ordinary
+-- role, so its objects are reachable only through required-extension
+-- membership.
+SET ROLE regress_ext_reqowner;
+CREATE EXTENSION test_ext_overload_req_dep SCHEMA test_reqdep;
+RESET ROLE;
+SET ROLE regress_ext_attacker;
+CREATE FUNCTION test_req.reqcall(int) RETURNS text
+    AS $$ SELECT 'attacker'::text $$ LANGUAGE sql IMMUTABLE;
+CREATE FUNCTION test_req.reqeq(int, int) RETURNS boolean
+    AS $$ SELECT false $$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR test_req.=== (leftarg = integer, rightarg = integer,
+                              function = test_req.reqeq);
+-- Resolving to this domain would run pwn() with the script's privileges.
+CREATE FUNCTION test_req.pwn(text) RETURNS boolean
+    AS $$ BEGIN RAISE EXCEPTION 'attacker code executed'; END $$ LANGUAGE 
plpgsql;
+CREATE DOMAIN test_req.reqdom AS text CHECK (test_req.pwn(VALUE));
+CREATE TABLE test_req.reqtab(t text);
+RESET ROLE;
+-- Cache a resolution made outside any script, under the search_path the
+-- script will pin; that plan must not be reused inside the script.
+SET search_path = test_req, test_reqdep, pg_temp;
+SELECT test_reqdep.reqplpgsql() AS warmed_outside_script;
+RESET search_path;
+CREATE EXTENSION test_ext_overload_req SCHEMA test_req;
+-- Every reference resolved to the required extension's objects (in
+-- test_reqdep), not the planted ones in test_req.
+SELECT c.who, c.who_cached, n.nspname AS domain_schema
+  FROM test_req.captured c
+  JOIN pg_type t ON t.oid = c.dom
+  JOIN pg_namespace n ON n.oid = t.typnamespace;
+SELECT n.nspname AS operator_func_schema
+  FROM pg_operator o
+  JOIN pg_proc p ON p.oid = o.oprcode
+  JOIN pg_namespace n ON n.oid = p.pronamespace
+ WHERE o.oprname = '###' AND o.oprnamespace = 'test_req'::regnamespace;
+SELECT 'test_reqdep.reqtab' AS tbl, count(*) FROM test_reqdep.reqtab
+UNION ALL
+SELECT 'test_req.reqtab', count(*) FROM test_req.reqtab;
+SELECT n.nspname AS opfamily_member_schema
+  FROM pg_amop a
+  JOIN pg_opfamily f ON f.oid = a.amopfamily
+  JOIN pg_operator o ON o.oid = a.amopopr
+  JOIN pg_namespace n ON n.oid = o.oprnamespace
+ WHERE f.opfname = 'reqfam';
+-- Outside the script the cached plan is good again.
+SET search_path = test_req, test_reqdep, pg_temp;
+SELECT test_reqdep.reqplpgsql() AS after_script;
+RESET search_path;
+DROP EXTENSION test_ext_overload_req;
+DROP EXTENSION test_ext_overload_req_dep;
+DROP SCHEMA test_req CASCADE;
+DROP SCHEMA test_reqdep CASCADE;
+DO $$ BEGIN
+    EXECUTE format('REVOKE CREATE ON DATABASE %I FROM regress_ext_reqowner',
+                   current_database());
+END $$;
+DROP ROLE regress_ext_attacker;
+DROP ROLE regress_ext_reqowner;
diff --git a/src/test/modules/test_extensions/test_ext_overload--1.0.sql 
b/src/test/modules/test_extensions/test_ext_overload--1.0.sql
new file mode 100644
index 00000000000..63e60a1e817
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload--1.0.sql
@@ -0,0 +1,19 @@
+/* src/test/modules/test_extensions/test_ext_overload--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_overload" to load this file. \quit
+
+-- f() and ### take varchar.  Resolving f('abc') during this script must
+-- reach them, not a planted f(text) sibling.
+CREATE FUNCTION @[email protected](varchar) RETURNS text
+    AS $$ SELECT 'extension'::text $$ LANGUAGE sql IMMUTABLE;
+
+CREATE FUNCTION @[email protected](varchar, varchar) RETURNS text
+    AS $$ SELECT 'extension'::text $$ LANGUAGE sql IMMUTABLE;
+
+CREATE OPERATOR @extschema@.### (leftarg = varchar, rightarg = varchar,
+                                 function = @[email protected]);
+
+CREATE TABLE @[email protected] AS
+    SELECT @[email protected]('abc') AS fn,
+           ('a' OPERATOR(@extschema@.###) 'b') AS op;
diff --git a/src/test/modules/test_extensions/test_ext_overload.control 
b/src/test/modules/test_extensions/test_ext_overload.control
new file mode 100644
index 00000000000..efaef1cb554
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload.control
@@ -0,0 +1,3 @@
+comment = 'Test protection against overload capture during extension scripts'
+default_version = '1.0'
+relocatable = false
diff --git 
a/src/test/modules/test_extensions/test_ext_overload_nosuper--1.0--2.0.sql 
b/src/test/modules/test_extensions/test_ext_overload_nosuper--1.0--2.0.sql
new file mode 100644
index 00000000000..f63cfcc02a6
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_nosuper--1.0--2.0.sql
@@ -0,0 +1,9 @@
+/* src/test/modules/test_extensions/test_ext_overload_nosuper--1.0--2.0.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION test_ext_overload_nosuper UPDATE" to load this 
file. \quit
+
+-- g() belongs to this extension but is owned by the non-superuser who
+-- installed 1.0, so an update run by any other role reaches it only by
+-- extension membership.
+CREATE TABLE @[email protected] AS SELECT g('abc') AS fn;
diff --git 
a/src/test/modules/test_extensions/test_ext_overload_nosuper--1.0.sql 
b/src/test/modules/test_extensions/test_ext_overload_nosuper--1.0.sql
new file mode 100644
index 00000000000..c3e93110825
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_nosuper--1.0.sql
@@ -0,0 +1,12 @@
+/* src/test/modules/test_extensions/test_ext_overload_nosuper--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_overload_nosuper" to load this file. \quit
+
+-- The script runs as the invoking non-superuser, so g() is owned by that
+-- role.  g('abc') must still resolve to it, not to a planted g(text).
+CREATE FUNCTION @[email protected](varchar) RETURNS text
+    AS $$ SELECT 'extension'::text $$ LANGUAGE sql IMMUTABLE;
+
+CREATE TABLE @[email protected]_nosuper AS
+    SELECT @[email protected]('abc') AS fn;
diff --git a/src/test/modules/test_extensions/test_ext_overload_nosuper.control 
b/src/test/modules/test_extensions/test_ext_overload_nosuper.control
new file mode 100644
index 00000000000..eb748089e70
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_nosuper.control
@@ -0,0 +1,4 @@
+comment = 'Test overload-capture protection for a superuser = false extension'
+default_version = '1.0'
+relocatable = false
+superuser = false
diff --git 
a/src/test/modules/test_extensions/test_ext_overload_parallel--1.0.sql 
b/src/test/modules/test_extensions/test_ext_overload_parallel--1.0.sql
new file mode 100644
index 00000000000..14a46bca590
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_parallel--1.0.sql
@@ -0,0 +1,15 @@
+/* src/test/modules/test_extensions/test_ext_overload_parallel--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_overload_parallel" to load this file. 
\quit
+
+-- wrap() is parallel safe and its body is parsed at run time, so under
+-- debug_parallel_query the worker resolves probe('x').  It must reach
+-- probe(varchar), not a planted probe(text).
+CREATE FUNCTION @[email protected](varchar) RETURNS text
+    AS $$ SELECT 'extension'::text $$ LANGUAGE sql IMMUTABLE PARALLEL SAFE;
+
+CREATE FUNCTION @[email protected]() RETURNS text
+    LANGUAGE plpgsql PARALLEL SAFE AS $$ BEGIN RETURN probe('x'); END $$;
+
+CREATE TABLE @[email protected] AS SELECT @[email protected]() AS who;
diff --git 
a/src/test/modules/test_extensions/test_ext_overload_parallel.control 
b/src/test/modules/test_extensions/test_ext_overload_parallel.control
new file mode 100644
index 00000000000..ba3cbae2162
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_parallel.control
@@ -0,0 +1,3 @@
+comment = 'Test overload-capture protection when resolution runs in a parallel 
worker'
+default_version = '1.0'
+relocatable = false
diff --git a/src/test/modules/test_extensions/test_ext_overload_req--1.0.sql 
b/src/test/modules/test_extensions/test_ext_overload_req--1.0.sql
new file mode 100644
index 00000000000..2e7d82a0e34
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_req--1.0.sql
@@ -0,0 +1,20 @@
+/* src/test/modules/test_extensions/test_ext_overload_req--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_overload_req" to load this file. \quit
+
+-- Every reference below is to a required extension's object.  A same-named
+-- plant in @extschema@ must not capture any of them.
+CREATE TABLE @[email protected] AS
+    SELECT reqcall(1) AS who,
+           reqplpgsql() AS who_cached,
+           pg_catalog.pg_typeof('abc'::reqdom) AS dom;
+
+INSERT INTO reqtab VALUES ('from script');
+
+CREATE OPERATOR @extschema@.### (leftarg = integer, rightarg = integer,
+                                 function = reqeq);
+
+CREATE OPERATOR FAMILY @[email protected] USING btree;
+ALTER OPERATOR FAMILY @[email protected] USING btree ADD
+    OPERATOR 3 === (integer, integer);
diff --git a/src/test/modules/test_extensions/test_ext_overload_req.control 
b/src/test/modules/test_extensions/test_ext_overload_req.control
new file mode 100644
index 00000000000..947556db238
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_req.control
@@ -0,0 +1,4 @@
+comment = 'extension whose script references a required extension''s objects'
+default_version = '1.0'
+relocatable = false
+requires = 'test_ext_overload_req_dep'
diff --git 
a/src/test/modules/test_extensions/test_ext_overload_req_dep--1.0.sql 
b/src/test/modules/test_extensions/test_ext_overload_req_dep--1.0.sql
new file mode 100644
index 00000000000..be42553a86d
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_req_dep--1.0.sql
@@ -0,0 +1,23 @@
+/* src/test/modules/test_extensions/test_ext_overload_req_dep--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_overload_req_dep" to load this file. \quit
+
+-- Objects the dependent extension's script references by unqualified name.
+CREATE FUNCTION @[email protected](int) RETURNS text
+    AS $$ SELECT 'required'::text $$ LANGUAGE sql IMMUTABLE;
+
+CREATE FUNCTION @[email protected](int, int) RETURNS boolean
+    AS $$ SELECT true $$ LANGUAGE sql IMMUTABLE;
+
+CREATE DOMAIN @[email protected] AS text;
+
+CREATE TABLE @[email protected](t text);
+
+-- Parsed at run time, so a call before the dependent script caches a
+-- resolution made without trust checks.
+CREATE FUNCTION @[email protected]() RETURNS text
+    LANGUAGE plpgsql AS $$ BEGIN RETURN reqcall(1); END $$;
+
+CREATE OPERATOR @extschema@.=== (leftarg = integer, rightarg = integer,
+                                 function = @[email protected]);
diff --git a/src/test/modules/test_extensions/test_ext_overload_req_dep.control 
b/src/test/modules/test_extensions/test_ext_overload_req_dep.control
new file mode 100644
index 00000000000..f6dfb5fa874
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_req_dep.control
@@ -0,0 +1,4 @@
+comment = 'required extension providing objects for the overload-capture test'
+default_version = '1.0'
+relocatable = false
+superuser = false
diff --git a/src/test/modules/test_extensions/test_ext_overload_strict--1.0.sql 
b/src/test/modules/test_extensions/test_ext_overload_strict--1.0.sql
new file mode 100644
index 00000000000..13b4a8982ac
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_strict--1.0.sql
@@ -0,0 +1,9 @@
+/* src/test/modules/test_extensions/test_ext_overload_strict--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_overload_strict" to load this file. \quit
+
+-- helper_only() exists only as a planted definition, so the script must
+-- fail with "function does not exist".
+CREATE TABLE @[email protected] AS
+    SELECT @[email protected]_only('abc') AS r;
diff --git 
a/src/test/modules/test_extensions/test_ext_overload_strict--10.0.sql 
b/src/test/modules/test_extensions/test_ext_overload_strict--10.0.sql
new file mode 100644
index 00000000000..accce6106a7
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_strict--10.0.sql
@@ -0,0 +1,9 @@
+/* src/test/modules/test_extensions/test_ext_overload_strict--10.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_overload_strict" to load this file. \quit
+
+-- As 8.0, unqualified: resolved through search_path by
+-- TypenameGetTypidExtended.
+CREATE FUNCTION @[email protected](planted_dom) RETURNS text
+    AS $$ SELECT 'extension'::text $$ LANGUAGE sql IMMUTABLE;
diff --git a/src/test/modules/test_extensions/test_ext_overload_strict--2.0.sql 
b/src/test/modules/test_extensions/test_ext_overload_strict--2.0.sql
new file mode 100644
index 00000000000..f80b2d00c25
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_strict--2.0.sql
@@ -0,0 +1,9 @@
+/* src/test/modules/test_extensions/test_ext_overload_strict--2.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_overload_strict" to load this file. \quit
+
+-- As for helper_only() in 1.0, but for an operator: the only definition of
+-- @@@ is one an unprivileged user planted, so the script must refuse it.
+CREATE TABLE @[email protected] AS
+    SELECT ('a' OPERATOR(@extschema@.@@@) 'b') AS r;
diff --git a/src/test/modules/test_extensions/test_ext_overload_strict--3.0.sql 
b/src/test/modules/test_extensions/test_ext_overload_strict--3.0.sql
new file mode 100644
index 00000000000..edd01344236
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_strict--3.0.sql
@@ -0,0 +1,10 @@
+/* src/test/modules/test_extensions/test_ext_overload_strict--3.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_overload_strict" to load this file. \quit
+
+-- The only <<< (varchar, varchar) is a plant; naming it as COMMUTATOR must
+-- fail as "does not exist", not collide with it when making a shell.
+CREATE OPERATOR @extschema@.>>> (leftarg = varchar, rightarg = varchar,
+                                 function = @[email protected],
+                                 commutator = <<<);
diff --git a/src/test/modules/test_extensions/test_ext_overload_strict--4.0.sql 
b/src/test/modules/test_extensions/test_ext_overload_strict--4.0.sql
new file mode 100644
index 00000000000..5b7a26db851
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_strict--4.0.sql
@@ -0,0 +1,9 @@
+/* src/test/modules/test_extensions/test_ext_overload_strict--4.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_overload_strict" to load this file. \quit
+
+-- Trusted f(varchar) and planted f(text) are visible; f(1) matches neither,
+-- so the error is about the argument types.
+CREATE TABLE @[email protected] AS
+    SELECT @[email protected](1) AS r;
diff --git a/src/test/modules/test_extensions/test_ext_overload_strict--5.0.sql 
b/src/test/modules/test_extensions/test_ext_overload_strict--5.0.sql
new file mode 100644
index 00000000000..1ef3115eb1b
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_strict--5.0.sql
@@ -0,0 +1,8 @@
+/* src/test/modules/test_extensions/test_ext_overload_strict--5.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_overload_strict" to load this file. \quit
+
+-- As in 4.0, for an operator: integer operands match neither ###.
+CREATE TABLE @[email protected] AS
+    SELECT (1 OPERATOR(@extschema@.###) 2) AS r;
diff --git a/src/test/modules/test_extensions/test_ext_overload_strict--6.0.sql 
b/src/test/modules/test_extensions/test_ext_overload_strict--6.0.sql
new file mode 100644
index 00000000000..195e6d0e421
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_strict--6.0.sql
@@ -0,0 +1,8 @@
+/* src/test/modules/test_extensions/test_ext_overload_strict--6.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_overload_strict" to load this file. \quit
+
+-- The only &&& is outside the search path; report that, not trust.
+CREATE TABLE @[email protected] AS
+    SELECT ('a' &&& 'b') AS r;
diff --git a/src/test/modules/test_extensions/test_ext_overload_strict--7.0.sql 
b/src/test/modules/test_extensions/test_ext_overload_strict--7.0.sql
new file mode 100644
index 00000000000..44803f02f02
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_strict--7.0.sql
@@ -0,0 +1,12 @@
+/* src/test/modules/test_extensions/test_ext_overload_strict--7.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_overload_strict" to load this file. \quit
+
+-- A planted table reached by its schema-qualified name, as an upgrade script
+-- would reach a table it expects from an earlier version.  (CREATE ... IF NOT
+-- EXISTS over the plant is already refused on membership grounds, so the
+-- reference must be a bare one.)  The INSERT would write the extension's data
+-- into the attacker's table; the qualified lookup must refuse it as "does not
+-- exist", exactly as the unqualified one does.
+INSERT INTO @[email protected] VALUES ('extension');
diff --git a/src/test/modules/test_extensions/test_ext_overload_strict--8.0.sql 
b/src/test/modules/test_extensions/test_ext_overload_strict--8.0.sql
new file mode 100644
index 00000000000..a01aa1ddab4
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_strict--8.0.sql
@@ -0,0 +1,8 @@
+/* src/test/modules/test_extensions/test_ext_overload_strict--8.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_overload_strict" to load this file. \quit
+
+-- A planted domain reached by its schema-qualified name in a signature.
+CREATE FUNCTION @[email protected](@[email protected]_dom) RETURNS text
+    AS $$ SELECT 'extension'::text $$ LANGUAGE sql IMMUTABLE;
diff --git a/src/test/modules/test_extensions/test_ext_overload_strict--9.0.sql 
b/src/test/modules/test_extensions/test_ext_overload_strict--9.0.sql
new file mode 100644
index 00000000000..be796a24211
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_strict--9.0.sql
@@ -0,0 +1,7 @@
+/* src/test/modules/test_extensions/test_ext_overload_strict--9.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_overload_strict" to load this file. \quit
+
+-- As 7.0, unqualified: resolved through search_path by RelnameGetRelid.
+INSERT INTO cfg VALUES ('extension');
diff --git a/src/test/modules/test_extensions/test_ext_overload_strict.control 
b/src/test/modules/test_extensions/test_ext_overload_strict.control
new file mode 100644
index 00000000000..d537687c91f
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_overload_strict.control
@@ -0,0 +1,3 @@
+comment = 'Test refusal to call an untrusted function during extension scripts'
+default_version = '1.0'
+relocatable = false
-- 
2.47.3

Reply via email to