This is an automated email from the ASF dual-hosted git repository.

reshke pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudberry.git

commit d4d3cf5b8f2b6a85e94308e6f5292c14812dbbb2
Author: Tom Lane <[email protected]>
AuthorDate: Mon Feb 9 10:14:22 2026 -0500

    Harden _int_matchsel() against being attached to the wrong operator.
    
    While the preceding commit prevented such attachments from occurring
    in future, this one aims to prevent further abuse of any already-
    created operator that exposes _int_matchsel to the wrong data types.
    (No other contrib module has a vulnerable selectivity estimator.)
    
    We need only check that the Const we've found in the query is indeed
    of the type we expect (query_int), but there's a difficulty: as an
    extension type, query_int doesn't have a fixed OID that we could
    hard-code into the estimator.
    
    Therefore, the bulk of this patch consists of infrastructure to let
    an extension function securely look up the OID of a datatype
    belonging to the same extension.  (Extension authors have requested
    such functionality before, so we anticipate that this code will
    have additional non-security uses, and may soon be extended to allow
    looking up other kinds of SQL objects.)
    
    This is done by first finding the extension that owns the calling
    function (there can be only one), and then thumbing through the
    objects owned by that extension to find a type that has the desired
    name.  This is relatively expensive, especially for large extensions,
    so a simple cache is put in front of these lookups.
    
    Reported-by: Daniel Firer as part of zeroday.cloud
    Author: Tom Lane <[email protected]>
    Reviewed-by: Noah Misch <[email protected]>
    Security: CVE-2026-2004
    Backpatch-through: 14
---
 contrib/intarray/_int_selfuncs.c |  14 ++++-
 src/backend/catalog/pg_depend.c  |  73 ++++++++++++++++++++++
 src/backend/commands/extension.c | 130 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/dependency.h |   2 +
 src/include/commands/extension.h |   2 +
 src/tools/pgindent/typedefs.list |   1 +
 6 files changed, 221 insertions(+), 1 deletion(-)

diff --git a/contrib/intarray/_int_selfuncs.c b/contrib/intarray/_int_selfuncs.c
index d4793b0b638..015649ab334 100644
--- a/contrib/intarray/_int_selfuncs.c
+++ b/contrib/intarray/_int_selfuncs.c
@@ -19,6 +19,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "commands/extension.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -171,7 +172,18 @@ _int_matchsel(PG_FUNCTION_ARGS)
                PG_RETURN_FLOAT8(0.0);
        }
 
-       /* The caller made sure the const is a query, so get it now */
+       /*
+        * Verify that the Const is a query_int, else return a default estimate.
+        * (This could only fail if someone attached this estimator to the wrong
+        * operator.)
+        */
+       if (((Const *) other)->consttype !=
+               get_function_sibling_type(fcinfo->flinfo->fn_oid, "query_int"))
+       {
+               ReleaseVariableStats(vardata);
+               PG_RETURN_FLOAT8(DEFAULT_EQ_SEL);
+       }
+
        query = DatumGetQueryTypeP(((Const *) other)->constvalue);
 
        /* Empty query matches nothing */
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 02e0ce71a07..b3d1c2fba99 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -23,11 +23,13 @@
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_depend.h"
 #include "catalog/pg_extension.h"
+#include "catalog/pg_type.h"
 #include "commands/extension.h"
 #include "miscadmin.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 
 
 static bool isObjectPinned(const ObjectAddress *object);
@@ -812,6 +814,77 @@ getAutoExtensionsOfObject(Oid classId, Oid objectId)
        return result;
 }
 
+/*
+ * Look up a type belonging to an extension.
+ *
+ * Returns the type's OID, or InvalidOid if not found.
+ *
+ * Notice that the type is specified by name only, without a schema.
+ * That's because this will typically be used by relocatable extensions
+ * which can't make a-priori assumptions about which schema their objects
+ * are in.  As long as the extension only defines one type of this name,
+ * the answer is unique anyway.
+ *
+ * We might later add the ability to look up functions, operators, etc.
+ */
+Oid
+getExtensionType(Oid extensionOid, const char *typname)
+{
+       Oid                     result = InvalidOid;
+       Relation        depRel;
+       ScanKeyData key[3];
+       SysScanDesc scan;
+       HeapTuple       tup;
+
+       depRel = table_open(DependRelationId, AccessShareLock);
+
+       ScanKeyInit(&key[0],
+                               Anum_pg_depend_refclassid,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(ExtensionRelationId));
+       ScanKeyInit(&key[1],
+                               Anum_pg_depend_refobjid,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(extensionOid));
+       ScanKeyInit(&key[2],
+                               Anum_pg_depend_refobjsubid,
+                               BTEqualStrategyNumber, F_INT4EQ,
+                               Int32GetDatum(0));
+
+       scan = systable_beginscan(depRel, DependReferenceIndexId, true,
+                                                         NULL, 3, key);
+
+       while (HeapTupleIsValid(tup = systable_getnext(scan)))
+       {
+               Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup);
+
+               if (depform->classid == TypeRelationId &&
+                       depform->deptype == DEPENDENCY_EXTENSION)
+               {
+                       Oid                     typoid = depform->objid;
+                       HeapTuple       typtup;
+
+                       typtup = SearchSysCache1(TYPEOID, 
ObjectIdGetDatum(typoid));
+                       if (!HeapTupleIsValid(typtup))
+                               continue;               /* should we throw an 
error? */
+                       if (strcmp(NameStr(((Form_pg_type) 
GETSTRUCT(typtup))->typname),
+                                          typname) == 0)
+                       {
+                               result = typoid;
+                               ReleaseSysCache(typtup);
+                               break;                  /* no need to keep 
searching */
+                       }
+                       ReleaseSysCache(typtup);
+               }
+       }
+
+       systable_endscan(scan);
+
+       table_close(depRel, AccessShareLock);
+
+       return result;
+}
+
 /*
  * Detect whether a sequence is marked as "owned" by a column
  *
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 41e06471f4e..8ec1ffdf209 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -46,6 +46,7 @@
 #include "catalog/pg_depend.h"
 #include "catalog/pg_extension.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "cdb/cdbgang.h"
 #include "commands/alter.h"
@@ -63,10 +64,12 @@
 #include "utils/builtins.h"
 #include "utils/conffiles.h"
 #include "utils/fmgroids.h"
+#include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/syscache.h"
 #include "utils/varlena.h"
 
 #include "catalog/oid_dispatch.h"
@@ -114,7 +117,26 @@ typedef struct ExtensionVersionInfo
        struct ExtensionVersionInfo *previous;  /* current best predecessor */
 } ExtensionVersionInfo;
 
+/*
+ * Cache structure for get_function_sibling_type (and maybe later,
+ * allied lookup functions).
+ */
+typedef struct ExtensionSiblingCache
+{
+       struct ExtensionSiblingCache *next; /* list link */
+       /* lookup key: requesting function's OID and type name */
+       Oid                     reqfuncoid;
+       const char *typname;
+       bool            valid;                  /* is entry currently valid? */
+       uint32          exthash;                /* cache hash of owning 
extension's OID */
+       Oid                     typeoid;                /* OID associated with 
typname */
+} ExtensionSiblingCache;
+
+/* Head of linked list of ExtensionSiblingCache structs */
+static ExtensionSiblingCache *ext_sibling_list = NULL;
+
 /* Local functions */
+static void ext_sibling_callback(Datum arg, int cacheid, uint32 hashvalue);
 static List *find_update_path(List *evi_list,
                                                          ExtensionVersionInfo 
*evi_start,
                                                          ExtensionVersionInfo 
*evi_target,
@@ -264,6 +286,114 @@ get_extension_schema(Oid ext_oid)
        return result;
 }
 
+/*
+ * get_function_sibling_type - find a type belonging to same extension as func
+ *
+ * Returns the type's OID, or InvalidOid if not found.
+ *
+ * This is useful in extensions, which won't have fixed object OIDs.
+ * We work from the calling function's own OID, which it can get from its
+ * FunctionCallInfo parameter, and look up the owning extension and thence
+ * a type belonging to the same extension.
+ *
+ * Notice that the type is specified by name only, without a schema.
+ * That's because this will typically be used by relocatable extensions
+ * which can't make a-priori assumptions about which schema their objects
+ * are in.  As long as the extension only defines one type of this name,
+ * the answer is unique anyway.
+ *
+ * We might later add the ability to look up functions, operators, etc.
+ *
+ * This code is simply a frontend for some pg_depend lookups.  Those lookups
+ * are fairly expensive, so we provide a simple cache facility.  We assume
+ * that the passed typname is actually a C constant, or at least permanently
+ * allocated, so that we need not copy that string.
+ */
+Oid
+get_function_sibling_type(Oid funcoid, const char *typname)
+{
+       ExtensionSiblingCache *cache_entry;
+       Oid                     extoid;
+       Oid                     typeoid;
+
+       /*
+        * See if we have the answer cached.  Someday there may be enough 
callers
+        * to justify a hash table, but for now, a simple linked list is fine.
+        */
+       for (cache_entry = ext_sibling_list; cache_entry != NULL;
+                cache_entry = cache_entry->next)
+       {
+               if (funcoid == cache_entry->reqfuncoid &&
+                       strcmp(typname, cache_entry->typname) == 0)
+                       break;
+       }
+       if (cache_entry && cache_entry->valid)
+               return cache_entry->typeoid;
+
+       /*
+        * Nope, so do the expensive lookups.  We do not expect failures, so we 
do
+        * not cache negative results.
+        */
+       extoid = getExtensionOfObject(ProcedureRelationId, funcoid);
+       if (!OidIsValid(extoid))
+               return InvalidOid;
+       typeoid = getExtensionType(extoid, typname);
+       if (!OidIsValid(typeoid))
+               return InvalidOid;
+
+       /*
+        * Build, or revalidate, cache entry.
+        */
+       if (cache_entry == NULL)
+       {
+               /* Register invalidation hook if this is first entry */
+               if (ext_sibling_list == NULL)
+                       CacheRegisterSyscacheCallback(EXTENSIONOID,
+                                                                               
  ext_sibling_callback,
+                                                                               
  (Datum) 0);
+
+               /* Momentarily zero the space to ensure valid flag is false */
+               cache_entry = (ExtensionSiblingCache *)
+                       MemoryContextAllocZero(CacheMemoryContext,
+                                                                  
sizeof(ExtensionSiblingCache));
+               cache_entry->next = ext_sibling_list;
+               ext_sibling_list = cache_entry;
+       }
+
+       cache_entry->reqfuncoid = funcoid;
+       cache_entry->typname = typname;
+       cache_entry->exthash = GetSysCacheHashValue1(EXTENSIONOID,
+                                                                               
                 ObjectIdGetDatum(extoid));
+       cache_entry->typeoid = typeoid;
+       /* Mark it valid only once it's fully populated */
+       cache_entry->valid = true;
+
+       return typeoid;
+}
+
+/*
+ * ext_sibling_callback
+ *             Syscache inval callback function for EXTENSIONOID cache
+ *
+ * It seems sufficient to invalidate ExtensionSiblingCache entries when
+ * the owning extension's pg_extension entry is modified or deleted.
+ * Neither a requesting function's OID, nor the OID of the object it's
+ * looking for, could change without an extension update or drop/recreate.
+ */
+static void
+ext_sibling_callback(Datum arg, int cacheid, uint32 hashvalue)
+{
+       ExtensionSiblingCache *cache_entry;
+
+       for (cache_entry = ext_sibling_list; cache_entry != NULL;
+                cache_entry = cache_entry->next)
+       {
+               if (hashvalue == 0 ||
+                       cache_entry->exthash == hashvalue)
+                       cache_entry->valid = false;
+       }
+}
+
 /*
  * Utility functions to check validity of extension and version names
  */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 0ea7b3cda81..6a7ae2abea9 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -245,6 +245,8 @@ extern long changeDependenciesOn(Oid refClassId, Oid 
oldRefObjectId,
 extern Oid     getExtensionOfObject(Oid classId, Oid objectId);
 extern List *getAutoExtensionsOfObject(Oid classId, Oid objectId);
 
+extern Oid     getExtensionType(Oid extensionOid, const char *typname);
+
 extern bool sequenceIsOwned(Oid seqId, char deptype, Oid *tableId, int32 
*colId);
 extern List *getOwnedSequences(Oid relid);
 extern Oid     getIdentitySequence(Oid relid, AttrNumber attnum, bool 
missing_ok);
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 042ae6ba70d..f2e45cf59ea 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -50,6 +50,8 @@ extern char *get_extension_name(Oid ext_oid);
 extern Oid     get_extension_schema(Oid ext_oid);
 extern bool extension_file_exists(const char *extensionName);
 
+extern Oid     get_function_sibling_type(Oid funcoid, const char *typname);
+
 extern ObjectAddress AlterExtensionNamespace(const char *extensionName, const 
char *newschema,
                                                                                
         Oid *oldschema);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 97ae28337d3..0b1b1df3b4a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -737,6 +737,7 @@ ExtensibleNodeEntry
 ExtensibleNodeMethods
 ExtensionControlFile
 ExtensionInfo
+ExtensionSiblingCache
 ExtensionVersionInfo
 FDWCollateState
 FD_SET


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to