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 e5e92aebca287d6dd93a3bfdc2ed0898236fc8bb
Author: Dean Rasheed <[email protected]>
AuthorDate: Mon Aug 11 09:10:17 2025 +0100

    Fix security checks in selectivity estimation functions.
    
    Commit e2d4ef8de86 (the fix for CVE-2017-7484) added security checks
    to the selectivity estimation functions to prevent them from running
    user-supplied operators on data obtained from pg_statistic if the user
    lacks privileges to select from the underlying table. In cases
    involving inheritance/partitioning, those checks were originally
    performed against the child RTE (which for plain inheritance might
    actually refer to the parent table). Commit 553d2ec2710 then extended
    that to also check the parent RTE, allowing access if the user had
    permissions on either the parent or the child. It turns out, however,
    that doing any checks using the child RTE is incorrect, since
    securityQuals is set to NULL when creating an RTE for an inheritance
    child (whether it refers to the parent table or the child table), and
    therefore such checks do not correctly account for any RLS policies or
    security barrier views. Therefore, do the security checks using only
    the parent RTE. This is consistent with how RLS policies are applied,
    and the executor's ACL checks, both of which use only the parent
    table's permissions/policies. Similar checks are performed in the
    extended stats code, so update that in the same way, centralizing all
    the checks in a new function.
    
    In addition, note that these checks by themselves are insufficient to
    ensure that the user has access to the table's data because, in a
    query that goes via a view, they only check that the view owner has
    permissions on the underlying table, not that the current user has
    permissions on the view itself. In the selectivity estimation
    functions, there is no easy way to navigate from underlying tables to
    views, so add permissions checks for all views mentioned in the query
    to the planner startup code. If the user lacks permissions on a view,
    a permissions error will now be reported at planner-startup, and the
    selectivity estimation functions will not be run.
    
    Checking view permissions at planner-startup in this way is a little
    ugly, since the same checks will be repeated at executor-startup.
    Longer-term, it might be better to move all the permissions checks
    from the executor to the planner so that permissions errors can be
    reported sooner, instead of creating a plan that won't ever be run.
    However, such a change seems too far-reaching to be back-patched.
    
    Back-patch to all supported versions. In v13, there is the added
    complication that UPDATEs and DELETEs on inherited target tables are
    planned using inheritance_planner(), which plans each inheritance
    child table separately, so that the selectivity estimation functions
    do not know that they are dealing with a child table accessed via its
    parent. Handle that by checking access permissions on the top parent
    table at planner-startup, in the same way as we do for views. Any
    securityQuals on the top parent table are moved down to the child
    tables by inheritance_planner(), so they continue to be checked by the
    selectivity estimation functions.
    
    Author: Dean Rasheed <[email protected]>
    Reviewed-by: Tom Lane <[email protected]>
    Reviewed-by: Noah Misch <[email protected]>
    Backpatch-through: 13
    Security: CVE-2025-8713
---
 src/backend/executor/execMain.c           |   3 +-
 src/backend/optimizer/plan/planner.c      |  33 +++
 src/backend/statistics/extended_stats.c   | 108 +++----
 src/backend/utils/adt/selfuncs.c          | 460 ++++++++++++++++--------------
 src/include/executor/executor.h           |   1 +
 src/include/utils/selfuncs.h              |   4 +-
 src/test/regress/expected/privileges.out  |  13 +-
 src/test/regress/expected/rowsecurity.out |  73 ++++-
 src/test/regress/expected/stats_ext.out   |  60 +++-
 src/test/regress/sql/privileges.sql       |  12 +-
 src/test/regress/sql/rowsecurity.sql      |  51 +++-
 src/test/regress/sql/stats_ext.sql        |  39 +++
 12 files changed, 569 insertions(+), 288 deletions(-)

diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index dc23c17c576..deac62e4d9a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -180,7 +180,6 @@ static void ExecutePlan(QueryDesc *queryDesc,
                                                ScanDirection direction,
                                                DestReceiver *dest,
                                                bool execute_once);
-static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
 static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
                                                                                
 Bitmapset *modifiedCols,
                                                                                
 AclMode requiredPerms);
@@ -1523,7 +1522,7 @@ ExecCheckPermissions(List *rangeTable, List *rteperminfos,
  * ExecCheckOneRelPerms
  *             Check access permissions for a single relation.
  */
-static bool
+bool
 ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
        AclMode         requiredPerms;
diff --git a/src/backend/optimizer/plan/planner.c 
b/src/backend/optimizer/plan/planner.c
index 58602135656..94e8f4ff8a0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -66,6 +66,7 @@
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/dsm_impl.h"
+#include "utils/acl.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/selfuncs.h"
@@ -1144,6 +1145,38 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
                                bms_make_singleton(parse->resultRelation);
        }
 
+       /*
+        * This would be a convenient time to check access permissions for all
+        * relations mentioned in the query, since it would be better to fail 
now,
+        * before doing any detailed planning.  However, for historical reasons,
+        * we leave this to be done at executor startup.
+        *
+        * Note, however, that we do need to check access permissions for any 
view
+        * relations mentioned in the query, in order to prevent information 
being
+        * leaked by selectivity estimation functions, which only check view 
owner
+        * permissions on underlying tables (see all_rows_selectable() and its
+        * callers).  This is a little ugly, because it means that access
+        * permissions for views will be checked twice, which is another reason
+        * why it would be better to do all the ACL checks here.
+        */
+       foreach(l, parse->rtable)
+       {
+               RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+               if (rte->perminfoindex != 0 &&
+                       rte->relkind == RELKIND_VIEW)
+               {
+                       RTEPermissionInfo *perminfo;
+                       bool            result;
+
+                       perminfo = getRTEPermissionInfo(parse->rteperminfos, 
rte);
+                       result = ExecCheckOneRelPerms(perminfo);
+                       if (!result)
+                               aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_VIEW,
+                                                          
get_rel_name(perminfo->relid));
+               }
+       }
+
        /*
         * Preprocess RowMark information.  We need to do this after subquery
         * pullup, so that all base relations are present.
diff --git a/src/backend/statistics/extended_stats.c 
b/src/backend/statistics/extended_stats.c
index 2e0fd083216..5b881c6e5b3 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -1346,6 +1346,9 @@ choose_best_statistics(List *stats, char requiredkind, 
bool inh,
  *             so we can't cope with system columns.
  * *exprs: input/output parameter collecting primitive subclauses within
  *             the clause tree
+ * *leakproof: input/output parameter recording the leakproofness of the
+ *             clause tree.  This should be true initially, and will be set to 
false
+ *             if any operator function used in an OpExpr is not leakproof.
  *
  * Returns false if there is something we definitively can't handle.
  * On true return, we can proceed to match the *exprs against statistics.
@@ -1353,7 +1356,7 @@ choose_best_statistics(List *stats, char requiredkind, 
bool inh,
 static bool
 statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
                                                                          Index 
relid, Bitmapset **attnums,
-                                                                         List 
**exprs)
+                                                                         List 
**exprs, bool *leakproof)
 {
        /* Look inside any binary-compatible relabeling (as in 
examine_variable) */
        if (IsA(clause, RelabelType))
@@ -1388,7 +1391,6 @@ statext_is_compatible_clause_internal(PlannerInfo *root, 
Node *clause,
        /* (Var/Expr op Const) or (Const op Var/Expr) */
        if (is_opclause(clause))
        {
-               RangeTblEntry *rte = root->simple_rte_array[relid];
                OpExpr     *expr = (OpExpr *) clause;
                Node       *clause_expr;
 
@@ -1423,24 +1425,15 @@ statext_is_compatible_clause_internal(PlannerInfo 
*root, Node *clause,
                                return false;
                }
 
-               /*
-                * If there are any securityQuals on the RTE from security 
barrier
-                * views or RLS policies, then the user may not have access to 
all the
-                * table's data, and we must check that the operator is 
leak-proof.
-                *
-                * If the operator is leaky, then we must ignore this clause 
for the
-                * purposes of estimating with MCV lists, otherwise the 
operator might
-                * reveal values from the MCV list that the user doesn't have
-                * permission to see.
-                */
-               if (rte->securityQuals != NIL &&
-                       !get_func_leakproof(get_opcode(expr->opno)))
-                       return false;
+               /* Check if the operator is leakproof */
+               if (*leakproof)
+                       *leakproof = get_func_leakproof(get_opcode(expr->opno));
 
                /* Check (Var op Const) or (Const op Var) clauses by recursing. 
*/
                if (IsA(clause_expr, Var))
                        return statext_is_compatible_clause_internal(root, 
clause_expr,
-                                                                               
                                 relid, attnums, exprs);
+                                                                               
                                 relid, attnums,
+                                                                               
                                 exprs, leakproof);
 
                /* Otherwise we have (Expr op Const) or (Const op Expr). */
                *exprs = lappend(*exprs, clause_expr);
@@ -1450,7 +1443,6 @@ statext_is_compatible_clause_internal(PlannerInfo *root, 
Node *clause,
        /* Var/Expr IN Array */
        if (IsA(clause, ScalarArrayOpExpr))
        {
-               RangeTblEntry *rte = root->simple_rte_array[relid];
                ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) clause;
                Node       *clause_expr;
                bool            expronleft;
@@ -1490,24 +1482,15 @@ statext_is_compatible_clause_internal(PlannerInfo 
*root, Node *clause,
                                return false;
                }
 
-               /*
-                * If there are any securityQuals on the RTE from security 
barrier
-                * views or RLS policies, then the user may not have access to 
all the
-                * table's data, and we must check that the operator is 
leak-proof.
-                *
-                * If the operator is leaky, then we must ignore this clause 
for the
-                * purposes of estimating with MCV lists, otherwise the 
operator might
-                * reveal values from the MCV list that the user doesn't have
-                * permission to see.
-                */
-               if (rte->securityQuals != NIL &&
-                       !get_func_leakproof(get_opcode(expr->opno)))
-                       return false;
+               /* Check if the operator is leakproof */
+               if (*leakproof)
+                       *leakproof = get_func_leakproof(get_opcode(expr->opno));
 
                /* Check Var IN Array clauses by recursing. */
                if (IsA(clause_expr, Var))
                        return statext_is_compatible_clause_internal(root, 
clause_expr,
-                                                                               
                                 relid, attnums, exprs);
+                                                                               
                                 relid, attnums,
+                                                                               
                                 exprs, leakproof);
 
                /* Otherwise we have Expr IN Array. */
                *exprs = lappend(*exprs, clause_expr);
@@ -1544,7 +1527,8 @@ statext_is_compatible_clause_internal(PlannerInfo *root, 
Node *clause,
                         */
                        if (!statext_is_compatible_clause_internal(root,
                                                                                
                           (Node *) lfirst(lc),
-                                                                               
                           relid, attnums, exprs))
+                                                                               
                           relid, attnums, exprs,
+                                                                               
                           leakproof))
                                return false;
                }
 
@@ -1558,8 +1542,10 @@ statext_is_compatible_clause_internal(PlannerInfo *root, 
Node *clause,
 
                /* Check Var IS NULL clauses by recursing. */
                if (IsA(nt->arg, Var))
-                       return statext_is_compatible_clause_internal(root, 
(Node *) (nt->arg),
-                                                                               
                                 relid, attnums, exprs);
+                       return statext_is_compatible_clause_internal(root,
+                                                                               
                                 (Node *) (nt->arg),
+                                                                               
                                 relid, attnums,
+                                                                               
                                 exprs, leakproof);
 
                /* Otherwise we have Expr IS NULL. */
                *exprs = lappend(*exprs, nt->arg);
@@ -1598,11 +1584,9 @@ static bool
 statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
                                                         Bitmapset **attnums, 
List **exprs)
 {
-       RangeTblEntry *rte = root->simple_rte_array[relid];
-       RelOptInfo *rel = root->simple_rel_array[relid];
        RestrictInfo *rinfo;
        int                     clause_relid;
-       Oid                     userid;
+       bool            leakproof;
 
        /*
         * Special-case handling for bare BoolExpr AND clauses, because the
@@ -1642,18 +1626,31 @@ statext_is_compatible_clause(PlannerInfo *root, Node 
*clause, Index relid,
                clause_relid != relid)
                return false;
 
-       /* Check the clause and determine what attributes it references. */
+       /*
+        * Check the clause, determine what attributes it references, and 
whether
+        * it includes any non-leakproof operators.
+        */
+       leakproof = true;
        if (!statext_is_compatible_clause_internal(root, (Node *) rinfo->clause,
-                                                                               
           relid, attnums, exprs))
+                                                                               
           relid, attnums, exprs,
+                                                                               
           &leakproof))
                return false;
 
        /*
-        * Check that the user has permission to read all required attributes.
+        * If the clause includes any non-leakproof operators, check that the 
user
+        * has permission to read all required attributes, otherwise the 
operators
+        * might reveal values from the MCV list that the user doesn't have
+        * permission to see.  We require all rows to be selectable --- there 
must
+        * be no securityQuals from security barrier views or RLS policies.  See
+        * similar code in examine_variable(), examine_simple_variable(), and
+        * statistic_proc_security_check().
+        *
+        * Note that for an inheritance child, the permission checks are 
performed
+        * on the inheritance root parent, and whole-table select privilege on 
the
+        * parent doesn't guarantee that the user could read all columns of the
+        * child. Therefore we must check all referenced columns.
         */
-       userid = OidIsValid(rel->userid) ? rel->userid : GetUserId();
-
-       /* Table-level SELECT privilege is sufficient for all columns */
-       if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
+       if (!leakproof)
        {
                Bitmapset  *clause_attnums = NULL;
                int                     attnum = -1;
@@ -1678,26 +1675,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node 
*clause, Index relid,
                if (*exprs != NIL)
                        pull_varattnos((Node *) *exprs, relid, &clause_attnums);
 
-               attnum = -1;
-               while ((attnum = bms_next_member(clause_attnums, attnum)) >= 0)
-               {
-                       /* Undo the offset */
-                       AttrNumber      attno = attnum + 
FirstLowInvalidHeapAttributeNumber;
-
-                       if (attno == InvalidAttrNumber)
-                       {
-                               /* Whole-row reference, so must have access to 
all columns */
-                               if (pg_attribute_aclcheck_all(rte->relid, 
userid, ACL_SELECT,
-                                                                               
          ACLMASK_ALL) != ACLCHECK_OK)
-                                       return false;
-                       }
-                       else
-                       {
-                               if (pg_attribute_aclcheck(rte->relid, attno, 
userid,
-                                                                               
  ACL_SELECT) != ACLCHECK_OK)
-                                       return false;
-                       }
-               }
+               /* Must have permission to read all rows from these columns */
+               if (!all_rows_selectable(root, relid, clause_attnums))
+                       return false;
        }
 
        /* If we reach here, the clause is OK */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index b9fc6c9a5c0..7c6750fb8e6 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5122,8 +5122,8 @@ static void inline 
adjust_partition_table_statistic_for_parent(HeapTuple statsTu
  *             this query.  (Caution: this should be trusted for statistical
  *             purposes only, since we do not check indimmediate nor verify 
that
  *             the exact same definition of equality applies.)
- *     acl_ok: true if current user has permission to read the column(s)
- *             underlying the pg_statistic entry.  This is consulted by
+ *     acl_ok: true if current user has permission to read all table rows from
+ *             the column(s) underlying the pg_statistic entry.  This is 
consulted by
  *             statistic_proc_security_check().
  *
  * Caller is responsible for doing ReleaseVariableStats() before exiting.
@@ -5238,7 +5238,6 @@ examine_variable(PlannerInfo *root, Node *node, int 
varRelid,
                 */
                ListCell   *ilist;
                ListCell   *slist;
-               Oid                     userid;
 
                /*
                 * The nullingrels bits within the expression could prevent us 
from
@@ -5248,17 +5247,6 @@ examine_variable(PlannerInfo *root, Node *node, int 
varRelid,
                if (bms_overlap(varnos, root->outer_join_rels))
                        node = remove_nulling_relids(node, 
root->outer_join_rels, NULL);
 
-               /*
-                * Determine the user ID to use for privilege checks: either
-                * onerel->userid if it's set (e.g., in case we're accessing 
the table
-                * via a view), or the current user otherwise.
-                *
-                * If we drill down to child relations, we keep using the same 
userid:
-                * it's going to be the same anyway, due to how we set up the 
relation
-                * tree (q.v. build_simple_rel).
-                */
-               userid = OidIsValid(onerel->userid) ? onerel->userid : 
GetUserId();
-
                foreach(ilist, onerel->indexlist)
                {
                        IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist);
@@ -5326,69 +5314,32 @@ examine_variable(PlannerInfo *root, Node *node, int 
varRelid,
 
                                                        if 
(HeapTupleIsValid(vardata->statsTuple))
                                                        {
-                                                               /* Get index's 
table for permission check */
-                                                               RangeTblEntry 
*rte;
-
-                                                               rte = 
planner_rt_fetch(index->rel->relid, root);
-                                                               
Assert(rte->rtekind == RTE_RELATION);
-
                                                                /*
+                                                                * Test if user 
has permission to access all
+                                                                * rows from 
the index's table.
+                                                                *
                                                                 * For 
simplicity, we insist on the whole
                                                                 * table being 
selectable, rather than trying
                                                                 * to identify 
which column(s) the index
-                                                                * depends on.  
Also require all rows to be
-                                                                * selectable 
--- there must be no
-                                                                * 
securityQuals from security barrier views
-                                                                * or RLS 
policies.
+                                                                * depends on.
+                                                                *
+                                                                * Note that 
for an inheritance child,
+                                                                * permissions 
are checked on the inheritance
+                                                                * root parent, 
and whole-table select
+                                                                * privilege on 
the parent doesn't quite
+                                                                * guarantee 
that the user could read all
+                                                                * columns of 
the child.  But in practice it's
+                                                                * unlikely 
that any interesting security
+                                                                * violation 
could result from allowing access
+                                                                * to the 
expression index's stats, so we
+                                                                * allow it 
anyway.  See similar code in
+                                                                * 
examine_simple_variable() for additional
+                                                                * comments.
                                                                 */
                                                                vardata->acl_ok 
=
-                                                                       
rte->securityQuals == NIL &&
-                                                                       
(pg_class_aclcheck(rte->relid, userid,
-                                                                               
                           ACL_SELECT) == ACLCHECK_OK);
-
-                                                               /*
-                                                                * If the user 
doesn't have permissions to
-                                                                * access an 
inheritance child relation, check
-                                                                * the 
permissions of the table actually
-                                                                * mentioned in 
the query, since most likely
-                                                                * the user 
does have that permission.  Note
-                                                                * that 
whole-table select privilege on the
-                                                                * parent 
doesn't quite guarantee that the
-                                                                * user could 
read all columns of the child.
-                                                                * But in 
practice it's unlikely that any
-                                                                * interesting 
security violation could result
-                                                                * from 
allowing access to the expression
-                                                                * index's 
stats, so we allow it anyway.  See
-                                                                * similar code 
in examine_simple_variable()
-                                                                * for 
additional comments.
-                                                                */
-                                                               if 
(!vardata->acl_ok &&
-                                                                       
root->append_rel_array != NULL)
-                                                               {
-                                                                       
AppendRelInfo *appinfo;
-                                                                       Index   
        varno = index->rel->relid;
-
-                                                                       appinfo 
= root->append_rel_array[varno];
-                                                                       while 
(appinfo &&
-                                                                               
   planner_rt_fetch(appinfo->parent_relid,
-                                                                               
                                        root)->rtekind == RTE_RELATION)
-                                                                       {
-                                                                               
varno = appinfo->parent_relid;
-                                                                               
appinfo = root->append_rel_array[varno];
-                                                                       }
-                                                                       if 
(varno != index->rel->relid)
-                                                                       {
-                                                                               
/* Repeat access check on this rel */
-                                                                               
rte = planner_rt_fetch(varno, root);
-                                                                               
Assert(rte->rtekind == RTE_RELATION);
-
-                                                                               
vardata->acl_ok =
-                                                                               
        rte->securityQuals == NIL &&
-                                                                               
        (pg_class_aclcheck(rte->relid,
-                                                                               
                                           userid,
-                                                                               
                                           ACL_SELECT) == ACLCHECK_OK);
-                                                                       }
-                                                               }
+                                                                       
all_rows_selectable(root,
+                                                                               
                                index->rel->relid,
+                                                                               
                                NULL);
                                                        }
                                                        else
                                                        {
@@ -5469,58 +5420,26 @@ examine_variable(PlannerInfo *root, Node *node, int 
varRelid,
                                        vardata->freefunc = ReleaseDummy;
 
                                        /*
+                                        * Test if user has permission to 
access all rows from the
+                                        * table.
+                                        *
                                         * For simplicity, we insist on the 
whole table being
                                         * selectable, rather than trying to 
identify which
-                                        * column(s) the statistics object 
depends on.  Also
-                                        * require all rows to be selectable 
--- there must be no
-                                        * securityQuals from security barrier 
views or RLS
-                                        * policies.
-                                        */
-                                       vardata->acl_ok =
-                                               rte->securityQuals == NIL &&
-                                               (pg_class_aclcheck(rte->relid, 
userid,
-                                                                               
   ACL_SELECT) == ACLCHECK_OK);
-
-                                       /*
-                                        * If the user doesn't have permissions 
to access an
-                                        * inheritance child relation, check 
the permissions of
-                                        * the table actually mentioned in the 
query, since most
-                                        * likely the user does have that 
permission.  Note that
-                                        * whole-table select privilege on the 
parent doesn't
-                                        * quite guarantee that the user could 
read all columns of
-                                        * the child. But in practice it's 
unlikely that any
-                                        * interesting security violation could 
result from
-                                        * allowing access to the expression 
stats, so we allow it
-                                        * anyway.  See similar code in 
examine_simple_variable()
-                                        * for additional comments.
+                                        * column(s) the statistics object 
depends on.
+                                        *
+                                        * Note that for an inheritance child, 
permissions are
+                                        * checked on the inheritance root 
parent, and whole-table
+                                        * select privilege on the parent 
doesn't quite guarantee
+                                        * that the user could read all columns 
of the child.  But
+                                        * in practice it's unlikely that any 
interesting security
+                                        * violation could result from allowing 
access to the
+                                        * expression stats, so we allow it 
anyway.  See similar
+                                        * code in examine_simple_variable() 
for additional
+                                        * comments.
                                         */
-                                       if (!vardata->acl_ok &&
-                                               root->append_rel_array != NULL)
-                                       {
-                                               AppendRelInfo *appinfo;
-                                               Index           varno = 
onerel->relid;
-
-                                               appinfo = 
root->append_rel_array[varno];
-                                               while (appinfo &&
-                                                          
planner_rt_fetch(appinfo->parent_relid,
-                                                                               
                root)->rtekind == RTE_RELATION)
-                                               {
-                                                       varno = 
appinfo->parent_relid;
-                                                       appinfo = 
root->append_rel_array[varno];
-                                               }
-                                               if (varno != onerel->relid)
-                                               {
-                                                       /* Repeat access check 
on this rel */
-                                                       rte = 
planner_rt_fetch(varno, root);
-                                                       Assert(rte->rtekind == 
RTE_RELATION);
-
-                                                       vardata->acl_ok =
-                                                               
rte->securityQuals == NIL &&
-                                                               
(pg_class_aclcheck(rte->relid,
-                                                                               
                   userid,
-                                                                               
                   ACL_SELECT) == ACLCHECK_OK);
-                                               }
-                                       }
+                                       vardata->acl_ok = 
all_rows_selectable(root,
+                                                                               
                                  onerel->relid,
+                                                                               
                                  NULL);
 
                                        break;
                                }
@@ -5630,96 +5549,20 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
                if (HeapTupleIsValid(vardata->statsTuple))
                {
-                       RelOptInfo *onerel = find_base_rel(root, var->varno);
-                       Oid                     userid;
-
                        /*
-                        * Check if user has permission to read this column.  
We require
-                        * all rows to be accessible, so there must be no 
securityQuals
-                        * from security barrier views or RLS policies.  Use
-                        * onerel->userid if it's set, in case we're accessing 
the table
-                        * via a view.
+                        * Test if user has permission to read all rows from 
this column.
+                        *
+                        * This requires that the user has the appropriate 
SELECT
+                        * privileges and that there are no securityQuals from 
security
+                        * barrier views or RLS policies.  If that's not the 
case, then we
+                        * only permit leakproof functions to be passed 
pg_statistic data
+                        * in vardata, otherwise the functions might reveal 
data that the
+                        * user doesn't have permission to see --- see
+                        * statistic_proc_security_check().
                         */
-                       userid = OidIsValid(onerel->userid) ? onerel->userid : 
GetUserId();
-
                        vardata->acl_ok =
-                               rte->securityQuals == NIL &&
-                               ((pg_class_aclcheck(rte->relid, userid,
-                                                                       
ACL_SELECT) == ACLCHECK_OK) ||
-                                (pg_attribute_aclcheck(rte->relid, 
var->varattno, userid,
-                                                                               
ACL_SELECT) == ACLCHECK_OK));
-
-                       /*
-                        * If the user doesn't have permissions to access an 
inheritance
-                        * child relation or specifically this attribute, check 
the
-                        * permissions of the table/column actually mentioned 
in the
-                        * query, since most likely the user does have that 
permission
-                        * (else the query will fail at runtime), and if the 
user can read
-                        * the column there then he can get the values of the 
child table
-                        * too.  To do that, we must find out which of the root 
parent's
-                        * attributes the child relation's attribute 
corresponds to.
-                        */
-                       if (!vardata->acl_ok && var->varattno > 0 &&
-                               root->append_rel_array != NULL)
-                       {
-                               AppendRelInfo *appinfo;
-                               Index           varno = var->varno;
-                               int                     varattno = 
var->varattno;
-                               bool            found = false;
-
-                               appinfo = root->append_rel_array[varno];
-
-                               /*
-                                * Partitions are mapped to their immediate 
parent, not the
-                                * root parent, so must be ready to walk up 
multiple
-                                * AppendRelInfos.  But stop if we hit a parent 
that is not
-                                * RTE_RELATION --- that's a flattened UNION 
ALL subquery, not
-                                * an inheritance parent.
-                                */
-                               while (appinfo &&
-                                          
planner_rt_fetch(appinfo->parent_relid,
-                                                                               
root)->rtekind == RTE_RELATION)
-                               {
-                                       int                     parent_varattno;
-
-                                       found = false;
-                                       if (varattno <= 0 || varattno > 
appinfo->num_child_cols)
-                                               break;  /* safety check */
-                                       parent_varattno = 
appinfo->parent_colnos[varattno - 1];
-                                       if (parent_varattno == 0)
-                                               break;  /* Var is local to 
child */
-
-                                       varno = appinfo->parent_relid;
-                                       varattno = parent_varattno;
-                                       found = true;
-
-                                       /* If the parent is itself a child, 
continue up. */
-                                       appinfo = root->append_rel_array[varno];
-                               }
-
-                               /*
-                                * In rare cases, the Var may be local to the 
child table, in
-                                * which case, we've got to live with having no 
access to this
-                                * column's stats.
-                                */
-                               if (!found)
-                                       return;
-
-                               /* Repeat the access check on this parent rel & 
column */
-                               rte = planner_rt_fetch(varno, root);
-                               Assert(rte->rtekind == RTE_RELATION);
-
-                               /*
-                                * Fine to use the same userid as it's the same 
in all
-                                * relations of a given inheritance tree.
-                                */
-                               vardata->acl_ok =
-                                       rte->securityQuals == NIL &&
-                                       ((pg_class_aclcheck(rte->relid, userid,
-                                                                               
ACL_SELECT) == ACLCHECK_OK) ||
-                                        (pg_attribute_aclcheck(rte->relid, 
varattno, userid,
-                                                                               
        ACL_SELECT) == ACLCHECK_OK));
-                       }
+                               all_rows_selectable(root, var->varno,
+                                                                       
bms_make_singleton(var->varattno - FirstLowInvalidHeapAttributeNumber));
                }
                else
                {
@@ -5843,17 +5686,212 @@ examine_simple_variable(PlannerInfo *root, Var *var,
        }
 }
 
+/*
+ * all_rows_selectable
+ *             Test whether the user has permission to select all rows from a 
given
+ *             relation.
+ *
+ * Inputs:
+ *     root: the planner info
+ *     varno: the index of the relation (assumed to be an RTE_RELATION)
+ *     varattnos: the attributes for which permission is required, or NULL if
+ *             whole-table access is required
+ *
+ * Returns true if the user has the required select permissions, and there are
+ * no securityQuals from security barrier views or RLS policies.
+ *
+ * Note that if the relation is an inheritance child relation, securityQuals
+ * and access permissions are checked against the inheritance root parent (the
+ * relation actually mentioned in the query) --- see the comments in
+ * expand_single_inheritance_child() for an explanation of why it has to be
+ * done this way.
+ *
+ * If varattnos is non-NULL, its attribute numbers should be offset by
+ * FirstLowInvalidHeapAttributeNumber so that system attributes can be
+ * checked.  If varattnos is NULL, only table-level SELECT privileges are
+ * checked, not any column-level privileges.
+ *
+ * Note: if the relation is accessed via a view, this function actually tests
+ * whether the view owner has permission to select from the relation.  To
+ * ensure that the current user has permission, it is also necessary to check
+ * that the current user has permission to select from the view, which we do
+ * at planner-startup --- see subquery_planner().
+ *
+ * This is exported so that other estimation functions can use it.
+ */
+bool
+all_rows_selectable(PlannerInfo *root, Index varno, Bitmapset *varattnos)
+{
+       RelOptInfo *rel = find_base_rel(root, varno);
+       RangeTblEntry *rte = planner_rt_fetch(varno, root);
+       Oid                     userid;
+       int                     varattno;
+
+       Assert(rte->rtekind == RTE_RELATION);
+
+       /*
+        * User ID to use for privilege checks (either the current user or the
+        * view owner, if we're accessing the table via a view).
+        *
+        * If we navigate up to a parent relation, we keep using the same 
userid,
+        * since it's the same in all relations of a given inheritance tree.
+        */
+       userid = OidIsValid(rel->userid) ? rel->userid : GetUserId();
+
+       /*
+        * Permissions and securityQuals must be checked on the table actually
+        * mentioned in the query, so if this is an inheritance child, navigate 
up
+        * to the inheritance root parent.  If the user can read the whole table
+        * or the required columns there, then they can read from the child 
table
+        * too.  For per-column checks, we must find out which of the root
+        * parent's attributes the child relation's attributes correspond to.
+        */
+       if (root->append_rel_array != NULL)
+       {
+               AppendRelInfo *appinfo;
+
+               appinfo = root->append_rel_array[varno];
+
+               /*
+                * Partitions are mapped to their immediate parent, not the root
+                * parent, so must be ready to walk up multiple AppendRelInfos. 
 But
+                * stop if we hit a parent that is not RTE_RELATION --- that's a
+                * flattened UNION ALL subquery, not an inheritance parent.
+                */
+               while (appinfo &&
+                          planner_rt_fetch(appinfo->parent_relid,
+                                                               root)->rtekind 
== RTE_RELATION)
+               {
+                       Bitmapset  *parent_varattnos = NULL;
+
+                       /*
+                        * For each child attribute, find the corresponding 
parent
+                        * attribute.  In rare cases, the attribute may be 
local to the
+                        * child table, in which case, we've got to live with 
having no
+                        * access to this column.
+                        */
+                       varattno = -1;
+                       while ((varattno = bms_next_member(varattnos, 
varattno)) >= 0)
+                       {
+                               AttrNumber      attno;
+                               AttrNumber      parent_attno;
+
+                               attno = varattno + 
FirstLowInvalidHeapAttributeNumber;
+
+                               if (attno == InvalidAttrNumber)
+                               {
+                                       /*
+                                        * Whole-row reference, so must map 
each column of the
+                                        * child to the parent table.
+                                        */
+                                       for (attno = 1; attno <= 
appinfo->num_child_cols; attno++)
+                                       {
+                                               parent_attno = 
appinfo->parent_colnos[attno - 1];
+                                               if (parent_attno == 0)
+                                                       return false;   /* attr 
is local to child */
+                                               parent_varattnos =
+                                                       
bms_add_member(parent_varattnos,
+                                                                               
   parent_attno - FirstLowInvalidHeapAttributeNumber);
+                                       }
+                               }
+                               else
+                               {
+                                       if (attno < 0)
+                                       {
+                                               /* System attnos are the same 
in all tables */
+                                               parent_attno = attno;
+                                       }
+                                       else
+                                       {
+                                               if (attno > 
appinfo->num_child_cols)
+                                                       return false;   /* 
safety check */
+                                               parent_attno = 
appinfo->parent_colnos[attno - 1];
+                                               if (parent_attno == 0)
+                                                       return false;   /* attr 
is local to child */
+                                       }
+                                       parent_varattnos =
+                                               bms_add_member(parent_varattnos,
+                                                                          
parent_attno - FirstLowInvalidHeapAttributeNumber);
+                               }
+                       }
+
+                       /* If the parent is itself a child, continue up */
+                       varno = appinfo->parent_relid;
+                       varattnos = parent_varattnos;
+                       appinfo = root->append_rel_array[varno];
+               }
+
+               /* Perform the access check on this parent rel */
+               rte = planner_rt_fetch(varno, root);
+               Assert(rte->rtekind == RTE_RELATION);
+       }
+
+       /*
+        * For all rows to be accessible, there must be no securityQuals from
+        * security barrier views or RLS policies.
+        */
+       if (rte->securityQuals != NIL)
+               return false;
+
+       /*
+        * Test for table-level SELECT privilege.
+        *
+        * If varattnos is non-NULL, this is sufficient to give access to all
+        * requested attributes, even for a child table, since we have verified
+        * that all required child columns have matching parent columns.
+        *
+        * If varattnos is NULL (whole-table access requested), this doesn't
+        * necessarily guarantee that the user can read all columns of a child
+        * table, but we allow it anyway (see comments in examine_variable()) 
and
+        * don't bother checking any column privileges.
+        */
+       if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) == ACLCHECK_OK)
+               return true;
+
+       if (varattnos == NULL)
+               return false;                   /* whole-table access requested 
*/
+
+       /*
+        * Don't have table-level SELECT privilege, so check per-column
+        * privileges.
+        */
+       varattno = -1;
+       while ((varattno = bms_next_member(varattnos, varattno)) >= 0)
+       {
+               AttrNumber      attno = varattno + 
FirstLowInvalidHeapAttributeNumber;
+
+               if (attno == InvalidAttrNumber)
+               {
+                       /* Whole-row reference, so must have access to all 
columns */
+                       if (pg_attribute_aclcheck_all(rte->relid, userid, 
ACL_SELECT,
+                                                                               
  ACLMASK_ALL) != ACLCHECK_OK)
+                               return false;
+               }
+               else
+               {
+                       if (pg_attribute_aclcheck(rte->relid, attno, userid,
+                                                                         
ACL_SELECT) != ACLCHECK_OK)
+                               return false;
+               }
+       }
+
+       /* If we reach here, have all required column privileges */
+       return true;
+}
+
 /*
  * Check whether it is permitted to call func_oid passing some of the
- * pg_statistic data in vardata.  We allow this either if the user has SELECT
- * privileges on the table or column underlying the pg_statistic data or if
- * the function is marked leak-proof.
+ * pg_statistic data in vardata.  We allow this if either of the following
+ * conditions is met: (1) the user has SELECT privileges on the table or
+ * column underlying the pg_statistic data and there are no securityQuals from
+ * security barrier views or RLS policies, or (2) the function is marked
+ * leakproof.
  */
 bool
 statistic_proc_security_check(VariableStatData *vardata, Oid func_oid)
 {
        if (vardata->acl_ok)
-               return true;
+               return true;                    /* have SELECT privs and no 
securityQuals */
 
        if (!OidIsValid(func_oid))
                return false;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 466097c94c7..18e04953f4e 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -238,6 +238,7 @@ extern void ExecutorRewind(QueryDesc *queryDesc);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType 
operation, ModifyTableState *mtstate);
 extern bool ExecCheckPermissions(List *rangeTable,
                                                                 List 
*rteperminfos, bool ereport_on_violation);
+extern bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
                                                          Relation 
resultRelationDesc,
                                                          Index 
resultRelationIndex,
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 6886a0a3cdb..9f8d5d93562 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -97,7 +97,8 @@ typedef struct VariableStatData
        Oid                     atttype;                /* actual type (after 
stripping relabel) */
        int32           atttypmod;              /* actual typmod (after 
stripping relabel) */
        bool            isunique;               /* matches unique index or 
DISTINCT clause */
-       bool            acl_ok;                 /* result of ACL check on table 
or column */
+       bool            acl_ok;                 /* true if user has SELECT 
privilege on all
+                                                                * rows from 
the table or column */
 } VariableStatData;
 
 #define ReleaseVariableStats(vardata)  \
@@ -151,6 +152,7 @@ extern PGDLLIMPORT get_index_stats_hook_type 
get_index_stats_hook;
 
 extern void examine_variable(PlannerInfo *root, Node *node, int varRelid,
                                                         VariableStatData 
*vardata);
+extern bool all_rows_selectable(PlannerInfo *root, Index varno, Bitmapset 
*varattnos);
 extern bool statistic_proc_security_check(VariableStatData *vardata, Oid 
func_oid);
 extern bool get_restriction_variable(PlannerInfo *root, List *args,
                                                                         int 
varRelid,
diff --git a/src/test/regress/expected/privileges.out 
b/src/test/regress/expected/privileges.out
index aff2b89051c..82c4805d49b 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -477,8 +477,6 @@ CREATE VIEW atest12v AS
   SELECT * FROM atest12 WHERE b <<< 5;
 CREATE VIEW atest12sbv WITH (security_barrier=true) AS
   SELECT * FROM atest12 WHERE b <<< 5;
-GRANT SELECT ON atest12v TO PUBLIC;
-GRANT SELECT ON atest12sbv TO PUBLIC;
 -- This plan should use nestloop, knowing that few rows will be selected.
 EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
                          QUERY PLAN                         
@@ -537,9 +535,18 @@ CREATE FUNCTION leak2(integer,integer) RETURNS boolean
   LANGUAGE plpgsql immutable;
 CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer,
                      restrict = scalargtsel);
--- This should not show any "leak" notices before failing.
+-- These should not show any "leak" notices before failing.
 EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0;
 ERROR:  permission denied for table atest12
+EXPLAIN (COSTS OFF) SELECT * FROM atest12v WHERE a >>> 0;
+ERROR:  permission denied for view atest12v
+EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv WHERE a >>> 0;
+ERROR:  permission denied for view atest12sbv
+-- Now regress_priv_user1 grants access to regress_priv_user2 via the views.
+SET SESSION AUTHORIZATION regress_priv_user1;
+GRANT SELECT ON atest12v TO PUBLIC;
+GRANT SELECT ON atest12sbv TO PUBLIC;
+SET SESSION AUTHORIZATION regress_priv_user2;
 -- These plans should continue to use a nestloop, since they execute with the
 -- privileges of the view owner.
 EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
diff --git a/src/test/regress/expected/rowsecurity.out 
b/src/test/regress/expected/rowsecurity.out
index 03fc362557b..71b0f828c5a 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -4612,7 +4612,7 @@ RESET SESSION AUTHORIZATION;
 DROP VIEW rls_view;
 DROP TABLE rls_tbl;
 DROP TABLE ref_tbl;
--- Leaky operator test
+-- Leaky operator tests
 CREATE TABLE rls_tbl (a int);
 INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x;
 ANALYZE rls_tbl;
@@ -4629,9 +4629,80 @@ SELECT * FROM rls_tbl WHERE a <<< 1000;
 ---
 (0 rows)
 
+RESET SESSION AUTHORIZATION;
+CREATE TABLE rls_child_tbl () INHERITS (rls_tbl);
+INSERT INTO rls_child_tbl SELECT x/10 FROM generate_series(1, 100) x;
+ANALYZE rls_child_tbl;
+CREATE TABLE rls_ptbl (a int) PARTITION BY RANGE (a);
+CREATE TABLE rls_part PARTITION OF rls_ptbl FOR VALUES FROM (-100) TO (100);
+INSERT INTO rls_ptbl SELECT x/10 FROM generate_series(1, 100) x;
+ANALYZE rls_ptbl, rls_part;
+ALTER TABLE rls_ptbl ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_part ENABLE ROW LEVEL SECURITY;
+GRANT SELECT ON rls_ptbl TO regress_rls_alice;
+GRANT SELECT ON rls_part TO regress_rls_alice;
+CREATE POLICY p1 ON rls_tbl USING (a < 0);
+CREATE POLICY p2 ON rls_ptbl USING (a < 0);
+CREATE POLICY p3 ON rls_part USING (a < 0);
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM rls_tbl WHERE a <<< 1000;
+ a 
+---
+(0 rows)
+
+SELECT * FROM rls_child_tbl WHERE a <<< 1000;
+ERROR:  permission denied for table rls_child_tbl
+SELECT * FROM rls_ptbl WHERE a <<< 1000;
+ a 
+---
+(0 rows)
+
+SELECT * FROM rls_part WHERE a <<< 1000;
+ a 
+---
+(0 rows)
+
+SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
+               SELECT * FROM rls_tbl) t WHERE a <<< 1000;
+ a 
+---
+(0 rows)
+
+SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
+               SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
+ERROR:  permission denied for table rls_child_tbl
+RESET SESSION AUTHORIZATION;
+REVOKE SELECT ON rls_tbl FROM regress_rls_alice;
+CREATE VIEW rls_tbl_view AS SELECT * FROM rls_tbl;
+ALTER TABLE rls_child_tbl ENABLE ROW LEVEL SECURITY;
+GRANT SELECT ON rls_child_tbl TO regress_rls_alice;
+CREATE POLICY p4 ON rls_child_tbl USING (a < 0);
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM rls_tbl WHERE a <<< 1000;
+ERROR:  permission denied for table rls_tbl
+SELECT * FROM rls_tbl_view WHERE a <<< 1000;
+ERROR:  permission denied for view rls_tbl_view
+SELECT * FROM rls_child_tbl WHERE a <<< 1000;
+ a 
+---
+(0 rows)
+
+SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
+               SELECT * FROM rls_tbl) t WHERE a <<< 1000;
+ERROR:  permission denied for table rls_tbl
+SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
+               SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
+ a 
+---
+(0 rows)
+
 DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
 RESET SESSION AUTHORIZATION;
+DROP TABLE rls_part;
+DROP TABLE rls_ptbl;
+DROP TABLE rls_child_tbl;
+DROP VIEW rls_tbl_view;
 DROP TABLE rls_tbl;
 -- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects
 SET SESSION AUTHORIZATION regress_rls_alice;
diff --git a/src/test/regress/expected/stats_ext.out 
b/src/test/regress/expected/stats_ext.out
index 80bf1733e89..e7303fd5b2a 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -3265,8 +3265,16 @@ CREATE FUNCTION op_leak(int, int) RETURNS bool
     LANGUAGE plpgsql;
 CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
                      restrict = scalarltsel);
+CREATE FUNCTION op_leak(record, record) RETURNS bool
+    AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END'
+    LANGUAGE plpgsql;
+CREATE OPERATOR <<< (procedure = op_leak, leftarg = record, rightarg = record,
+                     restrict = scalarltsel);
 SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission 
denied
 ERROR:  permission denied for table priv_test_tbl
+SELECT * FROM tststats.priv_test_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission 
denied
+ERROR:  permission denied for table priv_test_tbl
 DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission 
denied
 ERROR:  permission denied for table priv_test_tbl
 -- Grant access via a security barrier view, but hide all data
@@ -3281,10 +3289,17 @@ SELECT * FROM tststats.priv_test_view WHERE a <<< 0 AND 
b <<< 0; -- Should not l
 ---+---
 (0 rows)
 
+SELECT * FROM tststats.priv_test_view t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
+ a | b 
+---+---
+(0 rows)
+
 DELETE FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not 
leak
 -- Grant table access, but hide all data with RLS
 RESET SESSION AUTHORIZATION;
 ALTER TABLE tststats.priv_test_tbl ENABLE ROW LEVEL SECURITY;
+CREATE POLICY priv_test_tbl_pol ON tststats.priv_test_tbl USING (2 * a < 0);
 GRANT SELECT, DELETE ON tststats.priv_test_tbl TO regress_stats_user1;
 -- Should now have direct table access, but see nothing and leak nothing
 SET SESSION AUTHORIZATION regress_stats_user1;
@@ -3293,7 +3308,45 @@ SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b 
<<< 0; -- Should not le
 ---+---
 (0 rows)
 
+SELECT * FROM tststats.priv_test_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
+ a | b 
+---+---
+(0 rows)
+
 DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not 
leak
+-- Create plain inheritance parent table with no access permissions
+RESET SESSION AUTHORIZATION;
+CREATE TABLE tststats.priv_test_parent_tbl (a int, b int);
+ALTER TABLE tststats.priv_test_tbl INHERIT tststats.priv_test_parent_tbl;
+-- Should not have access to parent, and should leak nothing
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- 
Permission denied
+ERROR:  permission denied for table priv_test_parent_tbl
+SELECT * FROM tststats.priv_test_parent_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission 
denied
+ERROR:  permission denied for table priv_test_parent_tbl
+DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- 
Permission denied
+ERROR:  permission denied for table priv_test_parent_tbl
+-- Grant table access to parent, but hide all data with RLS
+RESET SESSION AUTHORIZATION;
+ALTER TABLE tststats.priv_test_parent_tbl ENABLE ROW LEVEL SECURITY;
+CREATE POLICY priv_test_parent_tbl_pol ON tststats.priv_test_parent_tbl USING 
(2 * a < 0);
+GRANT SELECT, DELETE ON tststats.priv_test_parent_tbl TO regress_stats_user1;
+-- Should now have direct table access to parent, but see nothing and leak 
nothing
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- 
Should not leak
+ a | b 
+---+---
+(0 rows)
+
+SELECT * FROM tststats.priv_test_parent_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
+ a | b 
+---+---
+(0 rows)
+
+DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should 
not leak
 -- privilege checks for pg_stats_ext and pg_stats_ext_exprs
 RESET SESSION AUTHORIZATION;
 CREATE TABLE stats_ext_tbl (id INT PRIMARY KEY GENERATED BY DEFAULT AS 
IDENTITY, col TEXT);
@@ -3373,11 +3426,14 @@ ALTER TABLE sts_sch1.tbl ALTER COLUMN a TYPE SMALLINT;
 -- Tidy up
 DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
+DROP OPERATOR <<< (record, record);
+DROP FUNCTION op_leak(record, record);
 RESET SESSION AUTHORIZATION;
 DROP TABLE stats_ext_tbl;
 DROP SCHEMA tststats CASCADE;
-NOTICE:  drop cascades to 2 other objects
-DETAIL:  drop cascades to table tststats.priv_test_tbl
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table tststats.priv_test_parent_tbl
+drop cascades to table tststats.priv_test_tbl
 drop cascades to view tststats.priv_test_view
 DROP SCHEMA sts_sch1, sts_sch2 CASCADE;
 NOTICE:  drop cascades to table sts_sch1.tbl
diff --git a/src/test/regress/sql/privileges.sql 
b/src/test/regress/sql/privileges.sql
index bf5c8067dd1..d530d14fd8b 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -348,8 +348,6 @@ CREATE VIEW atest12v AS
   SELECT * FROM atest12 WHERE b <<< 5;
 CREATE VIEW atest12sbv WITH (security_barrier=true) AS
   SELECT * FROM atest12 WHERE b <<< 5;
-GRANT SELECT ON atest12v TO PUBLIC;
-GRANT SELECT ON atest12sbv TO PUBLIC;
 
 -- This plan should use nestloop, knowing that few rows will be selected.
 EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
@@ -372,8 +370,16 @@ CREATE FUNCTION leak2(integer,integer) RETURNS boolean
 CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer,
                      restrict = scalargtsel);
 
--- This should not show any "leak" notices before failing.
+-- These should not show any "leak" notices before failing.
 EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM atest12v WHERE a >>> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv WHERE a >>> 0;
+
+-- Now regress_priv_user1 grants access to regress_priv_user2 via the views.
+SET SESSION AUTHORIZATION regress_priv_user1;
+GRANT SELECT ON atest12v TO PUBLIC;
+GRANT SELECT ON atest12sbv TO PUBLIC;
+SET SESSION AUTHORIZATION regress_priv_user2;
 
 -- These plans should continue to use a nestloop, since they execute with the
 -- privileges of the view owner.
diff --git a/src/test/regress/sql/rowsecurity.sql 
b/src/test/regress/sql/rowsecurity.sql
index 9f3db168808..6ffa1638bf1 100644
--- a/src/test/regress/sql/rowsecurity.sql
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -2151,7 +2151,7 @@ DROP VIEW rls_view;
 DROP TABLE rls_tbl;
 DROP TABLE ref_tbl;
 
--- Leaky operator test
+-- Leaky operator tests
 CREATE TABLE rls_tbl (a int);
 INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x;
 ANALYZE rls_tbl;
@@ -2166,9 +2166,58 @@ CREATE FUNCTION op_leak(int, int) RETURNS bool
 CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
                      restrict = scalarltsel);
 SELECT * FROM rls_tbl WHERE a <<< 1000;
+RESET SESSION AUTHORIZATION;
+
+CREATE TABLE rls_child_tbl () INHERITS (rls_tbl);
+INSERT INTO rls_child_tbl SELECT x/10 FROM generate_series(1, 100) x;
+ANALYZE rls_child_tbl;
+
+CREATE TABLE rls_ptbl (a int) PARTITION BY RANGE (a);
+CREATE TABLE rls_part PARTITION OF rls_ptbl FOR VALUES FROM (-100) TO (100);
+INSERT INTO rls_ptbl SELECT x/10 FROM generate_series(1, 100) x;
+ANALYZE rls_ptbl, rls_part;
+
+ALTER TABLE rls_ptbl ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_part ENABLE ROW LEVEL SECURITY;
+GRANT SELECT ON rls_ptbl TO regress_rls_alice;
+GRANT SELECT ON rls_part TO regress_rls_alice;
+CREATE POLICY p1 ON rls_tbl USING (a < 0);
+CREATE POLICY p2 ON rls_ptbl USING (a < 0);
+CREATE POLICY p3 ON rls_part USING (a < 0);
+
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM rls_tbl WHERE a <<< 1000;
+SELECT * FROM rls_child_tbl WHERE a <<< 1000;
+SELECT * FROM rls_ptbl WHERE a <<< 1000;
+SELECT * FROM rls_part WHERE a <<< 1000;
+SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
+               SELECT * FROM rls_tbl) t WHERE a <<< 1000;
+SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
+               SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
+RESET SESSION AUTHORIZATION;
+
+REVOKE SELECT ON rls_tbl FROM regress_rls_alice;
+CREATE VIEW rls_tbl_view AS SELECT * FROM rls_tbl;
+
+ALTER TABLE rls_child_tbl ENABLE ROW LEVEL SECURITY;
+GRANT SELECT ON rls_child_tbl TO regress_rls_alice;
+CREATE POLICY p4 ON rls_child_tbl USING (a < 0);
+
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM rls_tbl WHERE a <<< 1000;
+SELECT * FROM rls_tbl_view WHERE a <<< 1000;
+SELECT * FROM rls_child_tbl WHERE a <<< 1000;
+SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
+               SELECT * FROM rls_tbl) t WHERE a <<< 1000;
+SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
+               SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
 DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
 RESET SESSION AUTHORIZATION;
+DROP TABLE rls_part;
+DROP TABLE rls_ptbl;
+DROP TABLE rls_child_tbl;
+DROP VIEW rls_tbl_view;
 DROP TABLE rls_tbl;
 
 -- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects
diff --git a/src/test/regress/sql/stats_ext.sql 
b/src/test/regress/sql/stats_ext.sql
index 5ac9587c8fa..2e236e0f623 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -1648,7 +1648,14 @@ CREATE FUNCTION op_leak(int, int) RETURNS bool
     LANGUAGE plpgsql;
 CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
                      restrict = scalarltsel);
+CREATE FUNCTION op_leak(record, record) RETURNS bool
+    AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END'
+    LANGUAGE plpgsql;
+CREATE OPERATOR <<< (procedure = op_leak, leftarg = record, rightarg = record,
+                     restrict = scalarltsel);
 SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission 
denied
+SELECT * FROM tststats.priv_test_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission 
denied
 DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission 
denied
 
 -- Grant access via a security barrier view, but hide all data
@@ -1660,18 +1667,48 @@ GRANT SELECT, DELETE ON tststats.priv_test_view TO 
regress_stats_user1;
 -- Should now have access via the view, but see nothing and leak nothing
 SET SESSION AUTHORIZATION regress_stats_user1;
 SELECT * FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not 
leak
+SELECT * FROM tststats.priv_test_view t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
 DELETE FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not 
leak
 
 -- Grant table access, but hide all data with RLS
 RESET SESSION AUTHORIZATION;
 ALTER TABLE tststats.priv_test_tbl ENABLE ROW LEVEL SECURITY;
+CREATE POLICY priv_test_tbl_pol ON tststats.priv_test_tbl USING (2 * a < 0);
 GRANT SELECT, DELETE ON tststats.priv_test_tbl TO regress_stats_user1;
 
 -- Should now have direct table access, but see nothing and leak nothing
 SET SESSION AUTHORIZATION regress_stats_user1;
 SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not 
leak
+SELECT * FROM tststats.priv_test_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
 DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not 
leak
 
+-- Create plain inheritance parent table with no access permissions
+RESET SESSION AUTHORIZATION;
+CREATE TABLE tststats.priv_test_parent_tbl (a int, b int);
+ALTER TABLE tststats.priv_test_tbl INHERIT tststats.priv_test_parent_tbl;
+
+-- Should not have access to parent, and should leak nothing
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- 
Permission denied
+SELECT * FROM tststats.priv_test_parent_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission 
denied
+DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- 
Permission denied
+
+-- Grant table access to parent, but hide all data with RLS
+RESET SESSION AUTHORIZATION;
+ALTER TABLE tststats.priv_test_parent_tbl ENABLE ROW LEVEL SECURITY;
+CREATE POLICY priv_test_parent_tbl_pol ON tststats.priv_test_parent_tbl USING 
(2 * a < 0);
+GRANT SELECT, DELETE ON tststats.priv_test_parent_tbl TO regress_stats_user1;
+
+-- Should now have direct table access to parent, but see nothing and leak 
nothing
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- 
Should not leak
+SELECT * FROM tststats.priv_test_parent_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
+DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should 
not leak
+
 -- privilege checks for pg_stats_ext and pg_stats_ext_exprs
 RESET SESSION AUTHORIZATION;
 CREATE TABLE stats_ext_tbl (id INT PRIMARY KEY GENERATED BY DEFAULT AS 
IDENTITY, col TEXT);
@@ -1733,6 +1770,8 @@ ALTER TABLE sts_sch1.tbl ALTER COLUMN a TYPE SMALLINT;
 -- Tidy up
 DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
+DROP OPERATOR <<< (record, record);
+DROP FUNCTION op_leak(record, record);
 RESET SESSION AUTHORIZATION;
 DROP TABLE stats_ext_tbl;
 DROP SCHEMA tststats CASCADE;


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

Reply via email to