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 30d15234a740b2b1ba9e0a1e9400eefecb65e95a
Author: Tom Lane <[email protected]>
AuthorDate: Mon Feb 9 10:07:31 2026 -0500

    Require superuser to install a non-built-in selectivity estimator.
    
    Selectivity estimators come in two flavors: those that make specific
    assumptions about the data types they are working with, and those
    that don't.  Most of the built-in estimators are of the latter kind
    and are meant to be safely attachable to any operator.  If the
    operator does not behave as the estimator expects, you might get a
    poor estimate, but it won't crash.
    
    However, estimators that do make datatype assumptions can malfunction
    if they are attached to the wrong operator, since then the data they
    get from pg_statistic may not be of the type they expect.  This can
    rise to the level of a security problem, even permitting arbitrary
    code execution by a user who has the ability to create SQL objects.
    
    To close this hole, establish a rule that built-in estimators are
    required to protect themselves against being called on the wrong type
    of data.  It does not seem practical however to expect estimators in
    extensions to reach a similar level of security, at least not in the
    near term.  Therefore, also establish a rule that superuser privilege
    is required to attach a non-built-in estimator to an operator.
    We expect that this restriction will have little negative impact on
    extensions, since estimators generally have to be written in C and
    thus superuser privilege is required to create them in the first
    place.
    
    This commit changes the privilege checks in CREATE/ALTER OPERATOR
    to enforce the rule about superuser privilege, and fixes a couple
    of built-in estimators that were making datatype assumptions without
    sufficiently checking that they're valid.
    
    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
---
 src/backend/commands/operatorcmds.c      | 57 +++++++++++++++++++++++++-------
 src/backend/tsearch/ts_selfuncs.c        |  8 +++--
 src/backend/utils/adt/network_selfuncs.c | 48 ++++++++++++++++++---------
 3 files changed, 83 insertions(+), 30 deletions(-)

diff --git a/src/backend/commands/operatorcmds.c 
b/src/backend/commands/operatorcmds.c
index 51530eb2f56..47e4a02edb9 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -309,7 +309,6 @@ ValidateRestrictionEstimator(List *restrictionName)
 {
        Oid                     typeId[4];
        Oid                     restrictionOid;
-       AclResult       aclresult;
 
        typeId[0] = INTERNALOID;        /* PlannerInfo */
        typeId[1] = OIDOID;                     /* operator OID */
@@ -325,11 +324,33 @@ ValidateRestrictionEstimator(List *restrictionName)
                                 errmsg("restriction estimator function %s must 
return type %s",
                                                
NameListToString(restrictionName), "float8")));
 
-       /* Require EXECUTE rights for the estimator */
-       aclresult = object_aclcheck(ProcedureRelationId, restrictionOid, 
GetUserId(), ACL_EXECUTE);
-       if (aclresult != ACLCHECK_OK)
-               aclcheck_error(aclresult, OBJECT_FUNCTION,
-                                          NameListToString(restrictionName));
+       /*
+        * If the estimator is not a built-in function, require superuser
+        * privilege to install it.  This protects against using something that 
is
+        * not a restriction estimator or has hard-wired assumptions about what
+        * data types it is working with.  (Built-in estimators are required to
+        * defend themselves adequately against unexpected data type choices, 
but
+        * it seems impractical to expect that of extensions' estimators.)
+        *
+        * If it is built-in, only require EXECUTE rights.
+        */
+       if (restrictionOid >= FirstGenbkiObjectId)
+       {
+               if (!superuser())
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("must be superuser to specify a 
non-built-in restriction estimator function")));
+       }
+       else
+       {
+               AclResult       aclresult;
+
+               aclresult = object_aclcheck(ProcedureRelationId, restrictionOid,
+                                                                       
GetUserId(), ACL_EXECUTE);
+               if (aclresult != ACLCHECK_OK)
+                       aclcheck_error(aclresult, OBJECT_FUNCTION,
+                                                  
NameListToString(restrictionName));
+       }
 
        return restrictionOid;
 }
@@ -345,7 +366,6 @@ ValidateJoinEstimator(List *joinName)
        Oid                     typeId[5];
        Oid                     joinOid;
        Oid                     joinOid2;
-       AclResult       aclresult;
 
        typeId[0] = INTERNALOID;        /* PlannerInfo */
        typeId[1] = OIDOID;                     /* operator OID */
@@ -383,11 +403,24 @@ ValidateJoinEstimator(List *joinName)
                                 errmsg("join estimator function %s must return 
type %s",
                                                NameListToString(joinName), 
"float8")));
 
-       /* Require EXECUTE rights for the estimator */
-       aclresult = object_aclcheck(ProcedureRelationId, joinOid, GetUserId(), 
ACL_EXECUTE);
-       if (aclresult != ACLCHECK_OK)
-               aclcheck_error(aclresult, OBJECT_FUNCTION,
-                                          NameListToString(joinName));
+       /* privilege checks are the same as in ValidateRestrictionEstimator */
+       if (joinOid >= FirstGenbkiObjectId)
+       {
+               if (!superuser())
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("must be superuser to specify a 
non-built-in join estimator function")));
+       }
+       else
+       {
+               AclResult       aclresult;
+
+               aclresult = object_aclcheck(ProcedureRelationId, joinOid,
+                                                                       
GetUserId(), ACL_EXECUTE);
+               if (aclresult != ACLCHECK_OK)
+                       aclcheck_error(aclresult, OBJECT_FUNCTION,
+                                                  NameListToString(joinName));
+       }
 
        return joinOid;
 }
diff --git a/src/backend/tsearch/ts_selfuncs.c 
b/src/backend/tsearch/ts_selfuncs.c
index 92afc67a5c4..511ba6be7ff 100644
--- a/src/backend/tsearch/ts_selfuncs.c
+++ b/src/backend/tsearch/ts_selfuncs.c
@@ -109,12 +109,14 @@ tsmatchsel(PG_FUNCTION_ARGS)
         * OK, there's a Var and a Const we're dealing with here.  We need the
         * Const to be a TSQuery, else we can't do anything useful.  We have to
         * check this because the Var might be the TSQuery not the TSVector.
+        *
+        * Also check that the Var really is a TSVector, in case this estimator 
is
+        * mistakenly attached to some other operator.
         */
-       if (((Const *) other)->consttype == TSQUERYOID)
+       if (((Const *) other)->consttype == TSQUERYOID &&
+               vardata.vartype == TSVECTOROID)
        {
                /* tsvector @@ tsquery or the other way around */
-               Assert(vardata.vartype == TSVECTOROID);
-
                selec = tsquerysel(&vardata, ((Const *) other)->constvalue);
        }
        else
diff --git a/src/backend/utils/adt/network_selfuncs.c 
b/src/backend/utils/adt/network_selfuncs.c
index a8c0f954dfa..192df1c569d 100644
--- a/src/backend/utils/adt/network_selfuncs.c
+++ b/src/backend/utils/adt/network_selfuncs.c
@@ -43,9 +43,9 @@
 /* Maximum number of items to consider in join selectivity calculations */
 #define MAX_CONSIDERED_ELEMS 1024
 
-static Selectivity networkjoinsel_inner(Oid operator,
+static Selectivity networkjoinsel_inner(Oid operator, int opr_codenum,
                                                                                
VariableStatData *vardata1, VariableStatData *vardata2);
-static Selectivity networkjoinsel_semi(Oid operator,
+static Selectivity networkjoinsel_semi(Oid operator, int opr_codenum,
                                                                           
VariableStatData *vardata1, VariableStatData *vardata2);
 static Selectivity mcv_population(float4 *mcv_numbers, int mcv_nvalues);
 static Selectivity inet_hist_value_sel(Datum *values, int nvalues,
@@ -82,6 +82,7 @@ networksel(PG_FUNCTION_ARGS)
        Oid                     operator = PG_GETARG_OID(1);
        List       *args = (List *) PG_GETARG_POINTER(2);
        int                     varRelid = PG_GETARG_INT32(3);
+       int                     opr_codenum;
        VariableStatData vardata;
        Node       *other;
        bool            varonleft;
@@ -95,6 +96,14 @@ networksel(PG_FUNCTION_ARGS)
                                nullfrac;
        FmgrInfo        proc;
 
+       /*
+        * Before all else, verify that the operator is one of the ones 
supported
+        * by this function, which in turn proves that the input datatypes are
+        * what we expect.  Otherwise, attaching this selectivity function to 
some
+        * unexpected operator could cause trouble.
+        */
+       opr_codenum = inet_opr_codenum(operator);
+
        /*
         * If expression is not (variable op something) or (something op
         * variable), then punt and return a default estimate.
@@ -150,13 +159,12 @@ networksel(PG_FUNCTION_ARGS)
                                                 STATISTIC_KIND_HISTOGRAM, 
InvalidOid,
                                                 ATTSTATSSLOT_VALUES))
        {
-               int                     opr_codenum = 
inet_opr_codenum(operator);
+               int                     h_codenum;
 
                /* Commute if needed, so we can consider histogram to be on the 
left */
-               if (!varonleft)
-                       opr_codenum = -opr_codenum;
+               h_codenum = varonleft ? opr_codenum : -opr_codenum;
                non_mcv_selec = inet_hist_value_sel(hslot.values, hslot.nvalues,
-                                                                               
        constvalue, opr_codenum);
+                                                                               
        constvalue, h_codenum);
 
                free_attstatsslot(&hslot);
        }
@@ -203,10 +211,19 @@ networkjoinsel(PG_FUNCTION_ARGS)
 #endif
        SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) PG_GETARG_POINTER(4);
        double          selec;
+       int                     opr_codenum;
        VariableStatData vardata1;
        VariableStatData vardata2;
        bool            join_is_reversed;
 
+       /*
+        * Before all else, verify that the operator is one of the ones 
supported
+        * by this function, which in turn proves that the input datatypes are
+        * what we expect.  Otherwise, attaching this selectivity function to 
some
+        * unexpected operator could cause trouble.
+        */
+       opr_codenum = inet_opr_codenum(operator);
+
        get_join_variables(root, args, sjinfo,
                                           &vardata1, &vardata2, 
&join_is_reversed);
 
@@ -220,16 +237,19 @@ networkjoinsel(PG_FUNCTION_ARGS)
                         * Selectivity for left/full join is not exactly the 
same as inner
                         * join, but we neglect the difference, as eqjoinsel 
does.
                         */
-                       selec = networkjoinsel_inner(operator, &vardata1, 
&vardata2);
+                       selec = networkjoinsel_inner(operator, opr_codenum,
+                                                                               
 &vardata1, &vardata2);
                        break;
                case JOIN_SEMI:
                case JOIN_ANTI:
                case JOIN_LASJ_NOTIN:
                        /* Here, it's important that we pass the outer var on 
the left. */
                        if (!join_is_reversed)
-                               selec = networkjoinsel_semi(operator, 
&vardata1, &vardata2);
+                               selec = networkjoinsel_semi(operator, 
opr_codenum,
+                                                                               
        &vardata1, &vardata2);
                        else
                                selec = 
networkjoinsel_semi(get_commutator(operator),
+                                                                               
        -opr_codenum,
                                                                                
        &vardata2, &vardata1);
                        break;
                default:
@@ -261,7 +281,7 @@ networkjoinsel(PG_FUNCTION_ARGS)
  * Also, MCV vs histogram selectivity is not neglected as in eqjoinsel_inner().
  */
 static Selectivity
-networkjoinsel_inner(Oid operator,
+networkjoinsel_inner(Oid operator, int opr_codenum,
                                         VariableStatData *vardata1, 
VariableStatData *vardata2)
 {
        Form_pg_statistic stats;
@@ -274,7 +294,6 @@ networkjoinsel_inner(Oid operator,
                                mcv2_exists = false,
                                hist1_exists = false,
                                hist2_exists = false;
-       int                     opr_codenum;
        int                     mcv1_length = 0,
                                mcv2_length = 0;
        AttStatsSlot mcv1_slot;
@@ -326,8 +345,6 @@ networkjoinsel_inner(Oid operator,
                memset(&hist2_slot, 0, sizeof(hist2_slot));
        }
 
-       opr_codenum = inet_opr_codenum(operator);
-
        /*
         * Calculate selectivity for MCV vs MCV matches.
         */
@@ -388,7 +405,7 @@ networkjoinsel_inner(Oid operator,
  * histogram selectivity for semi/anti join cases.
  */
 static Selectivity
-networkjoinsel_semi(Oid operator,
+networkjoinsel_semi(Oid operator, int opr_codenum,
                                        VariableStatData *vardata1, 
VariableStatData *vardata2)
 {
        Form_pg_statistic stats;
@@ -402,7 +419,6 @@ networkjoinsel_semi(Oid operator,
                                mcv2_exists = false,
                                hist1_exists = false,
                                hist2_exists = false;
-       int                     opr_codenum;
        FmgrInfo        proc;
        int                     i,
                                mcv1_length = 0,
@@ -456,7 +472,6 @@ networkjoinsel_semi(Oid operator,
                memset(&hist2_slot, 0, sizeof(hist2_slot));
        }
 
-       opr_codenum = inet_opr_codenum(operator);
        fmgr_info(get_opcode(operator), &proc);
 
        /* Estimate number of input rows represented by RHS histogram. */
@@ -828,6 +843,9 @@ inet_semi_join_sel(Datum lhs_value,
 /*
  * Assign useful code numbers for the subnet inclusion/overlap operators
  *
+ * This will throw an error if the operator is not one of the ones we
+ * support in networksel() and networkjoinsel().
+ *
  * Only inet_masklen_inclusion_cmp() and inet_hist_match_divider() depend
  * on the exact codes assigned here; but many other places in this file
  * know that they can negate a code to obtain the code for the commutator


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

Reply via email to