Commit 215b43cd authored by Tom Lane's avatar Tom Lane

Improve RLS planning by marking individual quals with security levels.

In an RLS query, we must ensure that security filter quals are evaluated
before ordinary query quals, in case the latter contain "leaky" functions
that could expose the contents of sensitive rows.  The original
implementation of RLS planning ensured this by pushing the scan of a
secured table into a sub-query that it marked as a security-barrier view.
Unfortunately this results in very inefficient plans in many cases, because
the sub-query cannot be flattened and gets planned independently of the
rest of the query.

To fix, drop the use of sub-queries to enforce RLS qual order, and instead
mark each qual (RestrictInfo) with a security_level field establishing its
priority for evaluation.  Quals must be evaluated in security_level order,
except that "leakproof" quals can be allowed to go ahead of quals of lower
security_level, if it's helpful to do so.  This has to be enforced within
the ordering of any one list of quals to be evaluated at a table scan node,
and we also have to ensure that quals are not chosen for early evaluation
(i.e., use as an index qual or TID scan qual) if they're not allowed to go
ahead of other quals at the scan node.

This is sufficient to fix the problem for RLS quals, since we only support
RLS policies on simple tables and thus RLS quals will always exist at the
table scan level only.  Eventually these qual ordering rules should be
enforced for join quals as well, which would permit improving planning for
explicit security-barrier views; but that's a task for another patch.

Note that FDWs would need to be aware of these rules --- and not, for
example, send an insecure qual for remote execution --- but since we do
not yet allow RLS policies on foreign tables, the case doesn't arise.
This will need to be addressed before we can allow such policies.

Patch by me, reviewed by Stephen Frost and Dean Rasheed.

Discussion: https://postgr.es/m/8185.1477432701@sss.pgh.pa.us
parent aa17c06f
...@@ -2027,6 +2027,8 @@ _copyRestrictInfo(const RestrictInfo *from) ...@@ -2027,6 +2027,8 @@ _copyRestrictInfo(const RestrictInfo *from)
COPY_SCALAR_FIELD(outerjoin_delayed); COPY_SCALAR_FIELD(outerjoin_delayed);
COPY_SCALAR_FIELD(can_join); COPY_SCALAR_FIELD(can_join);
COPY_SCALAR_FIELD(pseudoconstant); COPY_SCALAR_FIELD(pseudoconstant);
COPY_SCALAR_FIELD(leakproof);
COPY_SCALAR_FIELD(security_level);
COPY_BITMAPSET_FIELD(clause_relids); COPY_BITMAPSET_FIELD(clause_relids);
COPY_BITMAPSET_FIELD(required_relids); COPY_BITMAPSET_FIELD(required_relids);
COPY_BITMAPSET_FIELD(outer_relids); COPY_BITMAPSET_FIELD(outer_relids);
......
...@@ -805,6 +805,7 @@ _equalRestrictInfo(const RestrictInfo *a, const RestrictInfo *b) ...@@ -805,6 +805,7 @@ _equalRestrictInfo(const RestrictInfo *a, const RestrictInfo *b)
COMPARE_NODE_FIELD(clause); COMPARE_NODE_FIELD(clause);
COMPARE_SCALAR_FIELD(is_pushed_down); COMPARE_SCALAR_FIELD(is_pushed_down);
COMPARE_SCALAR_FIELD(outerjoin_delayed); COMPARE_SCALAR_FIELD(outerjoin_delayed);
COMPARE_SCALAR_FIELD(security_level);
COMPARE_BITMAPSET_FIELD(required_relids); COMPARE_BITMAPSET_FIELD(required_relids);
COMPARE_BITMAPSET_FIELD(outer_relids); COMPARE_BITMAPSET_FIELD(outer_relids);
COMPARE_BITMAPSET_FIELD(nullable_relids); COMPARE_BITMAPSET_FIELD(nullable_relids);
......
...@@ -2059,6 +2059,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) ...@@ -2059,6 +2059,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
WRITE_FLOAT_FIELD(total_table_pages, "%.0f"); WRITE_FLOAT_FIELD(total_table_pages, "%.0f");
WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(tuple_fraction, "%.4f");
WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f");
WRITE_UINT_FIELD(qual_security_level);
WRITE_BOOL_FIELD(hasInheritedTarget); WRITE_BOOL_FIELD(hasInheritedTarget);
WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasJoinRTEs);
WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasLateralRTEs);
...@@ -2112,6 +2113,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) ...@@ -2112,6 +2113,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node)
WRITE_BOOL_FIELD(useridiscurrent); WRITE_BOOL_FIELD(useridiscurrent);
/* we don't try to print fdwroutine or fdw_private */ /* we don't try to print fdwroutine or fdw_private */
WRITE_NODE_FIELD(baserestrictinfo); WRITE_NODE_FIELD(baserestrictinfo);
WRITE_UINT_FIELD(baserestrict_min_security);
WRITE_NODE_FIELD(joininfo); WRITE_NODE_FIELD(joininfo);
WRITE_BOOL_FIELD(has_eclass_joins); WRITE_BOOL_FIELD(has_eclass_joins);
} }
...@@ -2195,6 +2197,8 @@ _outEquivalenceClass(StringInfo str, const EquivalenceClass *node) ...@@ -2195,6 +2197,8 @@ _outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
WRITE_BOOL_FIELD(ec_below_outer_join); WRITE_BOOL_FIELD(ec_below_outer_join);
WRITE_BOOL_FIELD(ec_broken); WRITE_BOOL_FIELD(ec_broken);
WRITE_UINT_FIELD(ec_sortref); WRITE_UINT_FIELD(ec_sortref);
WRITE_UINT_FIELD(ec_min_security);
WRITE_UINT_FIELD(ec_max_security);
} }
static void static void
...@@ -2261,6 +2265,8 @@ _outRestrictInfo(StringInfo str, const RestrictInfo *node) ...@@ -2261,6 +2265,8 @@ _outRestrictInfo(StringInfo str, const RestrictInfo *node)
WRITE_BOOL_FIELD(outerjoin_delayed); WRITE_BOOL_FIELD(outerjoin_delayed);
WRITE_BOOL_FIELD(can_join); WRITE_BOOL_FIELD(can_join);
WRITE_BOOL_FIELD(pseudoconstant); WRITE_BOOL_FIELD(pseudoconstant);
WRITE_BOOL_FIELD(leakproof);
WRITE_UINT_FIELD(security_level);
WRITE_BITMAPSET_FIELD(clause_relids); WRITE_BITMAPSET_FIELD(clause_relids);
WRITE_BITMAPSET_FIELD(required_relids); WRITE_BITMAPSET_FIELD(required_relids);
WRITE_BITMAPSET_FIELD(outer_relids); WRITE_BITMAPSET_FIELD(outer_relids);
......
...@@ -877,6 +877,108 @@ lateral reference. (Perhaps now that that stuff works, we could relax the ...@@ -877,6 +877,108 @@ lateral reference. (Perhaps now that that stuff works, we could relax the
pullup restriction?) pullup restriction?)
Security-level constraints on qual clauses
------------------------------------------
To support row-level security and security-barrier views efficiently,
we mark qual clauses (RestrictInfo nodes) with a "security_level" field.
The basic concept is that a qual with a lower security_level must be
evaluated before one with a higher security_level. This ensures that
"leaky" quals that might expose sensitive data are not evaluated until
after the security barrier quals that are supposed to filter out
security-sensitive rows. However, many qual conditions are "leakproof",
that is we trust the functions they use to not expose data. To avoid
unnecessarily inefficient plans, a leakproof qual is not delayed by
security-level considerations, even if it has a higher syntactic
security_level than another qual.
In a query that contains no use of RLS or security-barrier views, all
quals will have security_level zero, so that none of these restrictions
kick in; we don't even need to check leakproofness of qual conditions.
If there are security-barrier quals, they get security_level zero (and
possibly higher, if there are multiple layers of barriers). Regular quals
coming from the query text get a security_level one more than the highest
level used for barrier quals.
When new qual clauses are generated by EquivalenceClass processing,
they must be assigned a security_level. This is trickier than it seems.
One's first instinct is that it would be safe to use the largest level
found among the source quals for the EquivalenceClass, but that isn't
safe at all, because it allows unwanted delays of security-barrier quals.
Consider a barrier qual "t.x = t.y" plus a query qual "t.x = constant",
and suppose there is another query qual "leaky_function(t.z)" that
we mustn't evaluate before the barrier qual has been checked.
We will have an EC {t.x, t.y, constant} which will lead us to replace
the EC quals with "t.x = constant AND t.y = constant". (We do not want
to give up that behavior, either, since the latter condition could allow
use of an index on t.y, which we would never discover from the original
quals.) If these generated quals are assigned the same security_level as
the query quals, then it's possible for the leaky_function qual to be
evaluated first, allowing leaky_function to see data from rows that
possibly don't pass the barrier condition.
Instead, our handling of security levels with ECs works like this:
* Quals are not accepted as source clauses for ECs in the first place
unless they are leakproof or have security_level zero.
* EC-derived quals are assigned the minimum (not maximum) security_level
found among the EC's source clauses.
* If the maximum security_level found among the EC's source clauses is
above zero, then the equality operators selected for derived quals must
be leakproof. When no such operator can be found, the EC is treated as
"broken" and we fall back to emitting its source clauses without any
additional derived quals.
These rules together ensure that an untrusted qual clause (one with
security_level above zero) cannot cause an EC to generate a leaky derived
clause. This makes it safe to use the minimum not maximum security_level
for derived clauses. The rules could result in poor plans due to not
being able to generate derived clauses at all, but the risk of that is
small in practice because most btree equality operators are leakproof.
Also, by making exceptions for level-zero quals, we ensure that there is
no plan degradation when no barrier quals are present.
Once we have security levels assigned to all clauses, enforcement
of barrier-qual ordering restrictions boils down to two rules:
* Table scan plan nodes must not select quals for early execution
(for example, use them as index qualifiers in an indexscan) unless
they are leakproof or have security_level no higher than any other
qual that is due to be executed at the same plan node. (Use the
utility function restriction_is_securely_promotable() to check
whether it's okay to select a qual for early execution.)
* Normal execution of a list of quals must execute them in an order
that satisfies the same security rule, ie higher security_levels must
be evaluated later unless leakproof. (This is handled in a single place
by order_qual_clauses() in createplan.c.)
order_qual_clauses() uses a heuristic to decide exactly what to do with
leakproof clauses. Normally it sorts clauses by security_level then cost,
being careful that the sort is stable so that we don't reorder clauses
without a clear reason. But this could result in a very expensive qual
being done before a cheaper one that is of higher security_level.
If the cheaper qual is leaky we have no choice, but if it is leakproof
we could put it first. We choose to sort leakproof quals as if they
have security_level zero, but only when their cost is less than 10X
cpu_operator_cost; that restriction alleviates the opposite problem of
doing expensive quals first just because they're leakproof.
Additional rules will be needed to support safe handling of join quals
when there is a mix of security levels among join quals; for example, it
will be necessary to prevent leaky higher-security-level quals from being
evaluated at a lower join level than other quals of lower security level.
Currently there is no need to consider that since security-prioritized
quals can only be single-table restriction quals coming from RLS policies
or security-barrier views, and security-barrier view subqueries are never
flattened into the parent query. Hence enforcement of security-prioritized
quals only happens at the table scan level. With extra rules for safe
handling of security levels among join quals, it should be possible to let
security-barrier views be flattened into the parent query, allowing more
flexibility of planning while still preserving required ordering of qual
evaluation. But that will come later.
Post scan/join planning Post scan/join planning
----------------------- -----------------------
......
...@@ -896,9 +896,11 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, ...@@ -896,9 +896,11 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *childRTE; RangeTblEntry *childRTE;
RelOptInfo *childrel; RelOptInfo *childrel;
List *childquals; List *childquals;
Node *childqual; Index cq_min_security;
bool have_const_false_cq;
ListCell *parentvars; ListCell *parentvars;
ListCell *childvars; ListCell *childvars;
ListCell *lc;
/* append_rel_list contains all append rels; ignore others */ /* append_rel_list contains all append rels; ignore others */
if (appinfo->parent_relid != parentRTindex) if (appinfo->parent_relid != parentRTindex)
...@@ -921,34 +923,113 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, ...@@ -921,34 +923,113 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
* constraint exclusion; so do that first and then check to see if we * constraint exclusion; so do that first and then check to see if we
* can disregard this child. * can disregard this child.
* *
* As of 8.4, the child rel's targetlist might contain non-Var * The child rel's targetlist might contain non-Var expressions, which
* expressions, which means that substitution into the quals could * means that substitution into the quals could produce opportunities
* produce opportunities for const-simplification, and perhaps even * for const-simplification, and perhaps even pseudoconstant quals.
* pseudoconstant quals. To deal with this, we strip the RestrictInfo * Therefore, transform each RestrictInfo separately to see if it
* nodes, do the substitution, do const-simplification, and then * reduces to a constant or pseudoconstant. (We must process them
* reconstitute the RestrictInfo layer. * separately to keep track of the security level of each qual.)
*/
childquals = NIL;
cq_min_security = UINT_MAX;
have_const_false_cq = false;
foreach(lc, rel->baserestrictinfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
Node *childqual;
bool pseudoconstant;
Assert(IsA(rinfo, RestrictInfo));
childqual = adjust_appendrel_attrs(root,
(Node *) rinfo->clause,
appinfo);
childqual = eval_const_expressions(root, childqual);
/* check for flat-out constant */
if (childqual && IsA(childqual, Const))
{
if (((Const *) childqual)->constisnull ||
!DatumGetBool(((Const *) childqual)->constvalue))
{
/* Restriction reduces to constant FALSE or NULL */
have_const_false_cq = true;
break;
}
/* Restriction reduces to constant TRUE, so drop it */
continue;
}
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
!contain_vars_of_level(childqual, 0) &&
!contain_volatile_functions(childqual);
if (pseudoconstant)
{
/* tell createplan.c to check for gating quals */
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
childquals = lappend(childquals,
make_restrictinfo((Expr *) childqual,
rinfo->is_pushed_down,
rinfo->outerjoin_delayed,
pseudoconstant,
rinfo->security_level,
NULL, NULL, NULL));
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
/*
* In addition to the quals inherited from the parent, we might have
* securityQuals associated with this particular child node.
* (Currently this can only happen in appendrels originating from
* UNION ALL; inheritance child tables don't have their own
* securityQuals, see expand_inherited_rtentry().) Pull any such
* securityQuals up into the baserestrictinfo for the child. This is
* similar to process_security_barrier_quals() for the parent rel,
* except that we can't make any general deductions from such quals,
* since they don't hold for the whole appendrel.
*/
if (childRTE->securityQuals)
{
Index security_level = 0;
foreach(lc, childRTE->securityQuals)
{
List *qualset = (List *) lfirst(lc);
ListCell *lc2;
foreach(lc2, qualset)
{
Expr *qual = (Expr *) lfirst(lc2);
/* not likely that we'd see constants here, so no check */
childquals = lappend(childquals,
make_restrictinfo(qual,
true, false, false,
security_level,
NULL, NULL, NULL));
cq_min_security = Min(cq_min_security, security_level);
}
security_level++;
}
Assert(security_level <= root->qual_security_level);
}
/*
* OK, we've got all the baserestrictinfo quals for this child.
*/ */
childquals = get_all_actual_clauses(rel->baserestrictinfo); childrel->baserestrictinfo = childquals;
childquals = (List *) adjust_appendrel_attrs(root, childrel->baserestrict_min_security = cq_min_security;
(Node *) childquals,
appinfo); if (have_const_false_cq)
childqual = eval_const_expressions(root, (Node *)
make_ands_explicit(childquals));
if (childqual && IsA(childqual, Const) &&
(((Const *) childqual)->constisnull ||
!DatumGetBool(((Const *) childqual)->constvalue)))
{ {
/* /*
* Restriction reduces to constant FALSE or constant NULL after * Some restriction clause reduced to constant FALSE or NULL after
* substitution, so this child need not be scanned. * substitution, so this child need not be scanned.
*/ */
set_dummy_rel_pathlist(childrel); set_dummy_rel_pathlist(childrel);
continue; continue;
} }
childquals = make_ands_implicit((Expr *) childqual);
childquals = make_restrictinfos_from_actual_clauses(root,
childquals);
childrel->baserestrictinfo = childquals;
if (relation_excluded_by_constraints(root, childrel, childRTE)) if (relation_excluded_by_constraints(root, childrel, childRTE))
{ {
...@@ -1712,6 +1793,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, ...@@ -1712,6 +1793,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
} }
} }
rel->baserestrictinfo = upperrestrictlist; rel->baserestrictinfo = upperrestrictlist;
/* We don't bother recomputing baserestrict_min_security */
} }
pfree(safetyInfo.unsafeColumns); pfree(safetyInfo.unsafeColumns);
...@@ -2640,46 +2722,6 @@ subquery_push_qual(Query *subquery, RangeTblEntry *rte, Index rti, Node *qual) ...@@ -2640,46 +2722,6 @@ subquery_push_qual(Query *subquery, RangeTblEntry *rte, Index rti, Node *qual)
recurse_push_qual(subquery->setOperations, subquery, recurse_push_qual(subquery->setOperations, subquery,
rte, rti, qual); rte, rti, qual);
} }
else if (IsA(qual, CurrentOfExpr))
{
/*
* This is possible when a WHERE CURRENT OF expression is applied to a
* table with row-level security. In that case, the subquery should
* contain precisely one rtable entry for the table, and we can safely
* push the expression down into the subquery. This will cause a TID
* scan subquery plan to be generated allowing the target relation to
* be updated.
*
* Someday we might also be able to use a WHERE CURRENT OF expression
* on a view, but currently the rewriter prevents that, so we should
* never see any other case here, but generate sane error messages in
* case it does somehow happen.
*/
if (subquery->rtable == NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("WHERE CURRENT OF is not supported on a view with no underlying relation")));
if (list_length(subquery->rtable) > 1)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("WHERE CURRENT OF is not supported on a view with more than one underlying relation")));
if (subquery->hasAggs || subquery->groupClause || subquery->groupingSets || subquery->havingQual)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("WHERE CURRENT OF is not supported on a view with grouping or aggregation")));
/*
* Adjust the CURRENT OF expression to refer to the underlying table
* in the subquery, and attach it to the subquery's WHERE clause.
*/
qual = copyObject(qual);
((CurrentOfExpr *) qual)->cvarno = 1;
subquery->jointree->quals =
make_and_qual(subquery->jointree->quals, qual);
}
else else
{ {
/* /*
...@@ -2708,7 +2750,7 @@ subquery_push_qual(Query *subquery, RangeTblEntry *rte, Index rti, Node *qual) ...@@ -2708,7 +2750,7 @@ subquery_push_qual(Query *subquery, RangeTblEntry *rte, Index rti, Node *qual)
make_and_qual(subquery->jointree->quals, qual); make_and_qual(subquery->jointree->quals, qual);
/* /*
* We need not change the subquery's hasAggs or hasSublinks flags, * We need not change the subquery's hasAggs or hasSubLinks flags,
* since we can't be pushing down any aggregates that weren't there * since we can't be pushing down any aggregates that weren't there
* before, and we don't push down subselects at all. * before, and we don't push down subselects at all.
*/ */
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
*/ */
#include "postgres.h" #include "postgres.h"
#include <limits.h>
#include "access/stratnum.h" #include "access/stratnum.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "nodes/makefuncs.h" #include "nodes/makefuncs.h"
...@@ -78,9 +80,16 @@ static bool reconsider_full_join_clause(PlannerInfo *root, ...@@ -78,9 +80,16 @@ static bool reconsider_full_join_clause(PlannerInfo *root,
* care to mark an EquivalenceClass if it came from any such clauses. Also, * care to mark an EquivalenceClass if it came from any such clauses. Also,
* we have to check that both sides are either pseudo-constants or strict * we have to check that both sides are either pseudo-constants or strict
* functions of Vars, else they might not both go to NULL above the outer * functions of Vars, else they might not both go to NULL above the outer
* join. (This is the reason why we need a failure return. It's more * join. (This is the main reason why we need a failure return. It's more
* convenient to check this case here than at the call sites...) * convenient to check this case here than at the call sites...)
* *
* We also reject proposed equivalence clauses if they contain leaky functions
* and have security_level above zero. The EC evaluation rules require us to
* apply certain tests at certain joining levels, and we can't tolerate
* delaying any test on security_level grounds. By rejecting candidate clauses
* that might require security delays, we ensure it's safe to apply an EC
* clause as soon as it's supposed to be applied.
*
* On success return, we have also initialized the clause's left_ec/right_ec * On success return, we have also initialized the clause's left_ec/right_ec
* fields to point to the EquivalenceClass representing it. This saves lookup * fields to point to the EquivalenceClass representing it. This saves lookup
* effort later. * effort later.
...@@ -120,6 +129,10 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, ...@@ -120,6 +129,10 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo,
Assert(restrictinfo->left_ec == NULL); Assert(restrictinfo->left_ec == NULL);
Assert(restrictinfo->right_ec == NULL); Assert(restrictinfo->right_ec == NULL);
/* Reject if it is potentially postponable by security considerations */
if (restrictinfo->security_level > 0 && !restrictinfo->leakproof)
return false;
/* Extract info from given clause */ /* Extract info from given clause */
Assert(is_opclause(clause)); Assert(is_opclause(clause));
opno = ((OpExpr *) clause)->opno; opno = ((OpExpr *) clause)->opno;
...@@ -275,6 +288,10 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, ...@@ -275,6 +288,10 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo,
{ {
ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo); ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo);
ec1->ec_below_outer_join |= below_outer_join; ec1->ec_below_outer_join |= below_outer_join;
ec1->ec_min_security = Min(ec1->ec_min_security,
restrictinfo->security_level);
ec1->ec_max_security = Max(ec1->ec_max_security,
restrictinfo->security_level);
/* mark the RI as associated with this eclass */ /* mark the RI as associated with this eclass */
restrictinfo->left_ec = ec1; restrictinfo->left_ec = ec1;
restrictinfo->right_ec = ec1; restrictinfo->right_ec = ec1;
...@@ -306,6 +323,10 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, ...@@ -306,6 +323,10 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo,
ec1->ec_has_const |= ec2->ec_has_const; ec1->ec_has_const |= ec2->ec_has_const;
/* can't need to set has_volatile */ /* can't need to set has_volatile */
ec1->ec_below_outer_join |= ec2->ec_below_outer_join; ec1->ec_below_outer_join |= ec2->ec_below_outer_join;
ec1->ec_min_security = Min(ec1->ec_min_security,
ec2->ec_min_security);
ec1->ec_max_security = Max(ec1->ec_max_security,
ec2->ec_max_security);
ec2->ec_merged = ec1; ec2->ec_merged = ec1;
root->eq_classes = list_delete_ptr(root->eq_classes, ec2); root->eq_classes = list_delete_ptr(root->eq_classes, ec2);
/* just to avoid debugging confusion w/ dangling pointers: */ /* just to avoid debugging confusion w/ dangling pointers: */
...@@ -315,6 +336,10 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, ...@@ -315,6 +336,10 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo,
ec2->ec_relids = NULL; ec2->ec_relids = NULL;
ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo); ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo);
ec1->ec_below_outer_join |= below_outer_join; ec1->ec_below_outer_join |= below_outer_join;
ec1->ec_min_security = Min(ec1->ec_min_security,
restrictinfo->security_level);
ec1->ec_max_security = Max(ec1->ec_max_security,
restrictinfo->security_level);
/* mark the RI as associated with this eclass */ /* mark the RI as associated with this eclass */
restrictinfo->left_ec = ec1; restrictinfo->left_ec = ec1;
restrictinfo->right_ec = ec1; restrictinfo->right_ec = ec1;
...@@ -329,6 +354,10 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, ...@@ -329,6 +354,10 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo,
false, item2_type); false, item2_type);
ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo); ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo);
ec1->ec_below_outer_join |= below_outer_join; ec1->ec_below_outer_join |= below_outer_join;
ec1->ec_min_security = Min(ec1->ec_min_security,
restrictinfo->security_level);
ec1->ec_max_security = Max(ec1->ec_max_security,
restrictinfo->security_level);
/* mark the RI as associated with this eclass */ /* mark the RI as associated with this eclass */
restrictinfo->left_ec = ec1; restrictinfo->left_ec = ec1;
restrictinfo->right_ec = ec1; restrictinfo->right_ec = ec1;
...@@ -343,6 +372,10 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, ...@@ -343,6 +372,10 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo,
false, item1_type); false, item1_type);
ec2->ec_sources = lappend(ec2->ec_sources, restrictinfo); ec2->ec_sources = lappend(ec2->ec_sources, restrictinfo);
ec2->ec_below_outer_join |= below_outer_join; ec2->ec_below_outer_join |= below_outer_join;
ec2->ec_min_security = Min(ec2->ec_min_security,
restrictinfo->security_level);
ec2->ec_max_security = Max(ec2->ec_max_security,
restrictinfo->security_level);
/* mark the RI as associated with this eclass */ /* mark the RI as associated with this eclass */
restrictinfo->left_ec = ec2; restrictinfo->left_ec = ec2;
restrictinfo->right_ec = ec2; restrictinfo->right_ec = ec2;
...@@ -366,6 +399,8 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, ...@@ -366,6 +399,8 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo,
ec->ec_below_outer_join = below_outer_join; ec->ec_below_outer_join = below_outer_join;
ec->ec_broken = false; ec->ec_broken = false;
ec->ec_sortref = 0; ec->ec_sortref = 0;
ec->ec_min_security = restrictinfo->security_level;
ec->ec_max_security = restrictinfo->security_level;
ec->ec_merged = NULL; ec->ec_merged = NULL;
em1 = add_eq_member(ec, item1, item1_relids, item1_nullable_relids, em1 = add_eq_member(ec, item1, item1_relids, item1_nullable_relids,
false, item1_type); false, item1_type);
...@@ -639,6 +674,8 @@ get_eclass_for_sort_expr(PlannerInfo *root, ...@@ -639,6 +674,8 @@ get_eclass_for_sort_expr(PlannerInfo *root,
newec->ec_below_outer_join = false; newec->ec_below_outer_join = false;
newec->ec_broken = false; newec->ec_broken = false;
newec->ec_sortref = sortref; newec->ec_sortref = sortref;
newec->ec_min_security = UINT_MAX;
newec->ec_max_security = 0;
newec->ec_merged = NULL; newec->ec_merged = NULL;
if (newec->ec_has_volatile && sortref == 0) /* should not happen */ if (newec->ec_has_volatile && sortref == 0) /* should not happen */
...@@ -834,6 +871,7 @@ generate_base_implied_equalities_const(PlannerInfo *root, ...@@ -834,6 +871,7 @@ generate_base_implied_equalities_const(PlannerInfo *root,
bms_copy(ec->ec_relids), bms_copy(ec->ec_relids),
bms_union(cur_em->em_nullable_relids, bms_union(cur_em->em_nullable_relids,
const_em->em_nullable_relids), const_em->em_nullable_relids),
ec->ec_min_security,
ec->ec_below_outer_join, ec->ec_below_outer_join,
cur_em->em_is_const); cur_em->em_is_const);
} }
...@@ -890,6 +928,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root, ...@@ -890,6 +928,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
bms_copy(ec->ec_relids), bms_copy(ec->ec_relids),
bms_union(prev_em->em_nullable_relids, bms_union(prev_em->em_nullable_relids,
cur_em->em_nullable_relids), cur_em->em_nullable_relids),
ec->ec_min_security,
ec->ec_below_outer_join, ec->ec_below_outer_join,
false); false);
} }
...@@ -1313,7 +1352,13 @@ select_equality_operator(EquivalenceClass *ec, Oid lefttype, Oid righttype) ...@@ -1313,7 +1352,13 @@ select_equality_operator(EquivalenceClass *ec, Oid lefttype, Oid righttype)
opno = get_opfamily_member(opfamily, lefttype, righttype, opno = get_opfamily_member(opfamily, lefttype, righttype,
BTEqualStrategyNumber); BTEqualStrategyNumber);
if (OidIsValid(opno)) if (!OidIsValid(opno))
continue;
/* If no barrier quals in query, don't worry about leaky operators */
if (ec->ec_max_security == 0)
return opno;
/* Otherwise, insist that selected operators be leakproof */
if (get_func_leakproof(get_opcode(opno)))
return opno; return opno;
} }
return InvalidOid; return InvalidOid;
...@@ -1380,7 +1425,8 @@ create_join_clause(PlannerInfo *root, ...@@ -1380,7 +1425,8 @@ create_join_clause(PlannerInfo *root,
bms_union(leftem->em_relids, bms_union(leftem->em_relids,
rightem->em_relids), rightem->em_relids),
bms_union(leftem->em_nullable_relids, bms_union(leftem->em_nullable_relids,
rightem->em_nullable_relids)); rightem->em_nullable_relids),
ec->ec_min_security);
/* Mark the clause as redundant, or not */ /* Mark the clause as redundant, or not */
rinfo->parent_ec = parent_ec; rinfo->parent_ec = parent_ec;
...@@ -1691,7 +1737,8 @@ reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo, ...@@ -1691,7 +1737,8 @@ reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo,
innervar, innervar,
cur_em->em_expr, cur_em->em_expr,
bms_copy(inner_relids), bms_copy(inner_relids),
bms_copy(inner_nullable_relids)); bms_copy(inner_nullable_relids),
cur_ec->ec_min_security);
if (process_equivalence(root, newrinfo, true)) if (process_equivalence(root, newrinfo, true))
match = true; match = true;
} }
...@@ -1833,7 +1880,8 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo) ...@@ -1833,7 +1880,8 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo)
leftvar, leftvar,
cur_em->em_expr, cur_em->em_expr,
bms_copy(left_relids), bms_copy(left_relids),
bms_copy(left_nullable_relids)); bms_copy(left_nullable_relids),
cur_ec->ec_min_security);
if (process_equivalence(root, newrinfo, true)) if (process_equivalence(root, newrinfo, true))
matchleft = true; matchleft = true;
} }
...@@ -1847,7 +1895,8 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo) ...@@ -1847,7 +1895,8 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo)
rightvar, rightvar,
cur_em->em_expr, cur_em->em_expr,
bms_copy(right_relids), bms_copy(right_relids),
bms_copy(right_nullable_relids)); bms_copy(right_nullable_relids),
cur_ec->ec_min_security);
if (process_equivalence(root, newrinfo, true)) if (process_equivalence(root, newrinfo, true))
matchright = true; matchright = true;
} }
......
...@@ -2143,6 +2143,23 @@ match_clause_to_index(IndexOptInfo *index, ...@@ -2143,6 +2143,23 @@ match_clause_to_index(IndexOptInfo *index,
{ {
int indexcol; int indexcol;
/*
* Never match pseudoconstants to indexes. (Normally a match could not
* happen anyway, since a pseudoconstant clause couldn't contain a Var,
* but what if someone builds an expression index on a constant? It's not
* totally unreasonable to do so with a partial index, either.)
*/
if (rinfo->pseudoconstant)
return;
/*
* If clause can't be used as an indexqual because it must wait till after
* some lower-security-level restriction clause, reject it.
*/
if (!restriction_is_securely_promotable(rinfo, index->rel))
return;
/* OK, check each index column for a match */
for (indexcol = 0; indexcol < index->ncolumns; indexcol++) for (indexcol = 0; indexcol < index->ncolumns; indexcol++)
{ {
if (match_clause_to_indexcol(index, if (match_clause_to_indexcol(index,
...@@ -2237,15 +2254,6 @@ match_clause_to_indexcol(IndexOptInfo *index, ...@@ -2237,15 +2254,6 @@ match_clause_to_indexcol(IndexOptInfo *index,
Oid expr_coll; Oid expr_coll;
bool plain_op; bool plain_op;
/*
* Never match pseudoconstants to indexes. (Normally this could not
* happen anyway, since a pseudoconstant clause couldn't contain a Var,
* but what if someone builds an expression index on a constant? It's not
* totally unreasonable to do so with a partial index, either.)
*/
if (rinfo->pseudoconstant)
return false;
/* First check for boolean-index cases. */ /* First check for boolean-index cases. */
if (IsBooleanOpfamily(opfamily)) if (IsBooleanOpfamily(opfamily))
{ {
......
...@@ -43,12 +43,13 @@ ...@@ -43,12 +43,13 @@
#include "optimizer/clauses.h" #include "optimizer/clauses.h"
#include "optimizer/pathnode.h" #include "optimizer/pathnode.h"
#include "optimizer/paths.h" #include "optimizer/paths.h"
#include "optimizer/restrictinfo.h"
static bool IsTidEqualClause(OpExpr *node, int varno); static bool IsTidEqualClause(OpExpr *node, int varno);
static bool IsTidEqualAnyClause(ScalarArrayOpExpr *node, int varno); static bool IsTidEqualAnyClause(ScalarArrayOpExpr *node, int varno);
static List *TidQualFromExpr(Node *expr, int varno); static List *TidQualFromExpr(Node *expr, int varno);
static List *TidQualFromRestrictinfo(List *restrictinfo, int varno); static List *TidQualFromBaseRestrictinfo(RelOptInfo *rel);
/* /*
...@@ -216,24 +217,26 @@ TidQualFromExpr(Node *expr, int varno) ...@@ -216,24 +217,26 @@ TidQualFromExpr(Node *expr, int varno)
} }
/* /*
* Extract a set of CTID conditions from the given restrictinfo list * Extract a set of CTID conditions from the rel's baserestrictinfo list
*
* This is essentially identical to the AND case of TidQualFromExpr,
* except for the format of the input.
*/ */
static List * static List *
TidQualFromRestrictinfo(List *restrictinfo, int varno) TidQualFromBaseRestrictinfo(RelOptInfo *rel)
{ {
List *rlst = NIL; List *rlst = NIL;
ListCell *l; ListCell *l;
foreach(l, restrictinfo) foreach(l, rel->baserestrictinfo)
{ {
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
if (!IsA(rinfo, RestrictInfo)) /*
continue; /* probably should never happen */ * If clause must wait till after some lower-security-level
rlst = TidQualFromExpr((Node *) rinfo->clause, varno); * restriction clause, reject it.
*/
if (!restriction_is_securely_promotable(rinfo, rel))
continue;
rlst = TidQualFromExpr((Node *) rinfo->clause, rel->relid);
if (rlst) if (rlst)
break; break;
} }
...@@ -259,7 +262,7 @@ create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel) ...@@ -259,7 +262,7 @@ create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel)
*/ */
required_outer = rel->lateral_relids; required_outer = rel->lateral_relids;
tidquals = TidQualFromRestrictinfo(rel->baserestrictinfo, rel->relid); tidquals = TidQualFromBaseRestrictinfo(rel);
if (tidquals) if (tidquals)
add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals, add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
......
...@@ -4500,21 +4500,32 @@ get_switched_clauses(List *clauses, Relids outerrelids) ...@@ -4500,21 +4500,32 @@ get_switched_clauses(List *clauses, Relids outerrelids)
* plan node, sort the list into the order we want to check the quals * plan node, sort the list into the order we want to check the quals
* in at runtime. * in at runtime.
* *
* When security barrier quals are used in the query, we may have quals with
* different security levels in the list. Quals of lower security_level
* must go before quals of higher security_level, except that we can grant
* exceptions to move up quals that are leakproof. When security level
* doesn't force the decision, we prefer to order clauses by estimated
* execution cost, cheapest first.
*
* Ideally the order should be driven by a combination of execution cost and * Ideally the order should be driven by a combination of execution cost and
* selectivity, but it's not immediately clear how to account for both, * selectivity, but it's not immediately clear how to account for both,
* and given the uncertainty of the estimates the reliability of the decisions * and given the uncertainty of the estimates the reliability of the decisions
* would be doubtful anyway. So we just order by estimated per-tuple cost, * would be doubtful anyway. So we just order by security level then
* being careful not to change the order when (as is often the case) the * estimated per-tuple cost, being careful not to change the order when
* estimates are identical. * (as is often the case) the estimates are identical.
* *
* Although this will work on either bare clauses or RestrictInfos, it's * Although this will work on either bare clauses or RestrictInfos, it's
* much faster to apply it to RestrictInfos, since it can re-use cost * much faster to apply it to RestrictInfos, since it can re-use cost
* information that is cached in RestrictInfos. * information that is cached in RestrictInfos. XXX in the bare-clause
* case, we are also not able to apply security considerations. That is
* all right for the moment, because the bare-clause case doesn't occur
* anywhere that barrier quals could be present, but it would be better to
* get rid of it.
* *
* Note: some callers pass lists that contain entries that will later be * Note: some callers pass lists that contain entries that will later be
* removed; this is the easiest way to let this routine see RestrictInfos * removed; this is the easiest way to let this routine see RestrictInfos
* instead of bare clauses. It's OK because we only sort by cost, but * instead of bare clauses. This is another reason why trying to consider
* a cost/selectivity combination would likely do the wrong thing. * selectivity in the ordering would likely do the wrong thing.
*/ */
static List * static List *
order_qual_clauses(PlannerInfo *root, List *clauses) order_qual_clauses(PlannerInfo *root, List *clauses)
...@@ -4523,6 +4534,7 @@ order_qual_clauses(PlannerInfo *root, List *clauses) ...@@ -4523,6 +4534,7 @@ order_qual_clauses(PlannerInfo *root, List *clauses)
{ {
Node *clause; Node *clause;
Cost cost; Cost cost;
Index security_level;
} QualItem; } QualItem;
int nitems = list_length(clauses); int nitems = list_length(clauses);
QualItem *items; QualItem *items;
...@@ -4548,6 +4560,27 @@ order_qual_clauses(PlannerInfo *root, List *clauses) ...@@ -4548,6 +4560,27 @@ order_qual_clauses(PlannerInfo *root, List *clauses)
cost_qual_eval_node(&qcost, clause, root); cost_qual_eval_node(&qcost, clause, root);
items[i].clause = clause; items[i].clause = clause;
items[i].cost = qcost.per_tuple; items[i].cost = qcost.per_tuple;
if (IsA(clause, RestrictInfo))
{
RestrictInfo *rinfo = (RestrictInfo *) clause;
/*
* If a clause is leakproof, it doesn't have to be constrained by
* its nominal security level. If it's also reasonably cheap
* (here defined as 10X cpu_operator_cost), pretend it has
* security_level 0, which will allow it to go in front of
* more-expensive quals of lower security levels. Of course, that
* will also force it to go in front of cheaper quals of its own
* security level, which is not so great, but we can alleviate
* that risk by applying the cost limit cutoff.
*/
if (rinfo->leakproof && items[i].cost < 10 * cpu_operator_cost)
items[i].security_level = 0;
else
items[i].security_level = rinfo->security_level;
}
else
items[i].security_level = 0;
i++; i++;
} }
...@@ -4564,9 +4597,13 @@ order_qual_clauses(PlannerInfo *root, List *clauses) ...@@ -4564,9 +4597,13 @@ order_qual_clauses(PlannerInfo *root, List *clauses)
/* insert newitem into the already-sorted subarray */ /* insert newitem into the already-sorted subarray */
for (j = i; j > 0; j--) for (j = i; j > 0; j--)
{ {
if (newitem.cost >= items[j - 1].cost) QualItem *olditem = &items[j - 1];
if (newitem.security_level > olditem->security_level ||
(newitem.security_level == olditem->security_level &&
newitem.cost >= olditem->cost))
break; break;
items[j] = items[j - 1]; items[j] = *olditem;
} }
items[j] = newitem; items[j] = newitem;
} }
......
...@@ -51,6 +51,9 @@ static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode, ...@@ -51,6 +51,9 @@ static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
bool below_outer_join, bool below_outer_join,
Relids *qualscope, Relids *inner_join_rels, Relids *qualscope, Relids *inner_join_rels,
List **postponed_qual_list); List **postponed_qual_list);
static void process_security_barrier_quals(PlannerInfo *root,
int rti, Relids qualscope,
bool below_outer_join);
static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root, static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root,
Relids left_rels, Relids right_rels, Relids left_rels, Relids right_rels,
Relids inner_join_rels, Relids inner_join_rels,
...@@ -60,6 +63,7 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause, ...@@ -60,6 +63,7 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool is_deduced, bool is_deduced,
bool below_outer_join, bool below_outer_join,
JoinType jointype, JoinType jointype,
Index security_level,
Relids qualscope, Relids qualscope,
Relids ojscope, Relids ojscope,
Relids outerjoin_nonnullable, Relids outerjoin_nonnullable,
...@@ -745,8 +749,14 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, ...@@ -745,8 +749,14 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
{ {
int varno = ((RangeTblRef *) jtnode)->rtindex; int varno = ((RangeTblRef *) jtnode)->rtindex;
/* No quals to deal with, just return correct result */ /* qualscope is just the one RTE */
*qualscope = bms_make_singleton(varno); *qualscope = bms_make_singleton(varno);
/* Deal with any securityQuals attached to the RTE */
if (root->qual_security_level > 0)
process_security_barrier_quals(root,
varno,
*qualscope,
below_outer_join);
/* A single baserel does not create an inner join */ /* A single baserel does not create an inner join */
*inner_join_rels = NULL; *inner_join_rels = NULL;
joinlist = list_make1(jtnode); joinlist = list_make1(jtnode);
...@@ -810,6 +820,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, ...@@ -810,6 +820,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
if (bms_is_subset(pq->relids, *qualscope)) if (bms_is_subset(pq->relids, *qualscope))
distribute_qual_to_rels(root, pq->qual, distribute_qual_to_rels(root, pq->qual,
false, below_outer_join, JOIN_INNER, false, below_outer_join, JOIN_INNER,
root->qual_security_level,
*qualscope, NULL, NULL, NULL, *qualscope, NULL, NULL, NULL,
NULL); NULL);
else else
...@@ -825,6 +836,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, ...@@ -825,6 +836,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
distribute_qual_to_rels(root, qual, distribute_qual_to_rels(root, qual,
false, below_outer_join, JOIN_INNER, false, below_outer_join, JOIN_INNER,
root->qual_security_level,
*qualscope, NULL, NULL, NULL, *qualscope, NULL, NULL, NULL,
postponed_qual_list); postponed_qual_list);
} }
...@@ -1002,6 +1014,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, ...@@ -1002,6 +1014,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
distribute_qual_to_rels(root, qual, distribute_qual_to_rels(root, qual,
false, below_outer_join, j->jointype, false, below_outer_join, j->jointype,
root->qual_security_level,
*qualscope, *qualscope,
ojscope, nonnullable_rels, NULL, ojscope, nonnullable_rels, NULL,
postponed_qual_list); postponed_qual_list);
...@@ -1058,6 +1071,67 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, ...@@ -1058,6 +1071,67 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
return joinlist; return joinlist;
} }
/*
* process_security_barrier_quals
* Transfer security-barrier quals into relation's baserestrictinfo list.
*
* The rewriter put any relevant security-barrier conditions into the RTE's
* securityQuals field, but it's now time to copy them into the rel's
* baserestrictinfo.
*
* In inheritance cases, we only consider quals attached to the parent rel
* here; they will be valid for all children too, so it's okay to consider
* them for purposes like equivalence class creation. Quals attached to
* individual child rels will be dealt with during path creation.
*/
static void
process_security_barrier_quals(PlannerInfo *root,
int rti, Relids qualscope,
bool below_outer_join)
{
RangeTblEntry *rte = root->simple_rte_array[rti];
Index security_level = 0;
ListCell *lc;
/*
* Each element of the securityQuals list has been preprocessed into an
* implicitly-ANDed list of clauses. All the clauses in a given sublist
* should get the same security level, but successive sublists get higher
* levels.
*/
foreach(lc, rte->securityQuals)
{
List *qualset = (List *) lfirst(lc);
ListCell *lc2;
foreach(lc2, qualset)
{
Node *qual = (Node *) lfirst(lc2);
/*
* We cheat to the extent of passing ojscope = qualscope rather
* than its more logical value of NULL. The only effect this has
* is to force a Var-free qual to be evaluated at the rel rather
* than being pushed up to top of tree, which we don't want.
*/
distribute_qual_to_rels(root, qual,
false,
below_outer_join,
JOIN_INNER,
security_level,
qualscope,
qualscope,
NULL,
NULL,
NULL);
}
security_level++;
}
/* Assert that qual_security_level is higher than anything we just used */
Assert(security_level <= root->qual_security_level);
}
/* /*
* make_outerjoininfo * make_outerjoininfo
* Build a SpecialJoinInfo for the current outer join * Build a SpecialJoinInfo for the current outer join
...@@ -1516,6 +1590,7 @@ compute_semijoin_info(SpecialJoinInfo *sjinfo, List *clause) ...@@ -1516,6 +1590,7 @@ compute_semijoin_info(SpecialJoinInfo *sjinfo, List *clause)
* 'below_outer_join': TRUE if the qual is from a JOIN/ON that is below the * 'below_outer_join': TRUE if the qual is from a JOIN/ON that is below the
* nullable side of a higher-level outer join * nullable side of a higher-level outer join
* 'jointype': type of join the qual is from (JOIN_INNER for a WHERE clause) * 'jointype': type of join the qual is from (JOIN_INNER for a WHERE clause)
* 'security_level': security_level to assign to the qual
* 'qualscope': set of baserels the qual's syntactic scope covers * 'qualscope': set of baserels the qual's syntactic scope covers
* 'ojscope': NULL if not an outer-join qual, else the minimum set of baserels * 'ojscope': NULL if not an outer-join qual, else the minimum set of baserels
* needed to form this join * needed to form this join
...@@ -1545,6 +1620,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, ...@@ -1545,6 +1620,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool is_deduced, bool is_deduced,
bool below_outer_join, bool below_outer_join,
JoinType jointype, JoinType jointype,
Index security_level,
Relids qualscope, Relids qualscope,
Relids ojscope, Relids ojscope,
Relids outerjoin_nonnullable, Relids outerjoin_nonnullable,
...@@ -1794,6 +1870,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, ...@@ -1794,6 +1870,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
is_pushed_down, is_pushed_down,
outerjoin_delayed, outerjoin_delayed,
pseudoconstant, pseudoconstant,
security_level,
relids, relids,
outerjoin_nonnullable, outerjoin_nonnullable,
nullable_relids); nullable_relids);
...@@ -2142,6 +2219,9 @@ distribute_restrictinfo_to_rels(PlannerInfo *root, ...@@ -2142,6 +2219,9 @@ distribute_restrictinfo_to_rels(PlannerInfo *root,
/* Add clause to rel's restriction list */ /* Add clause to rel's restriction list */
rel->baserestrictinfo = lappend(rel->baserestrictinfo, rel->baserestrictinfo = lappend(rel->baserestrictinfo,
restrictinfo); restrictinfo);
/* Update security level info */
rel->baserestrict_min_security = Min(rel->baserestrict_min_security,
restrictinfo->security_level);
break; break;
case BMS_MULTIPLE: case BMS_MULTIPLE:
...@@ -2189,6 +2269,8 @@ distribute_restrictinfo_to_rels(PlannerInfo *root, ...@@ -2189,6 +2269,8 @@ distribute_restrictinfo_to_rels(PlannerInfo *root,
* caller because this function is used after deconstruct_jointree, so we * caller because this function is used after deconstruct_jointree, so we
* don't have knowledge of where the clause items came from.) * don't have knowledge of where the clause items came from.)
* *
* "security_level" is the security level to assign to the new restrictinfo.
*
* "both_const" indicates whether both items are known pseudo-constant; * "both_const" indicates whether both items are known pseudo-constant;
* in this case it is worth applying eval_const_expressions() in case we * in this case it is worth applying eval_const_expressions() in case we
* can produce constant TRUE or constant FALSE. (Otherwise it's not, * can produce constant TRUE or constant FALSE. (Otherwise it's not,
...@@ -2209,6 +2291,7 @@ process_implied_equality(PlannerInfo *root, ...@@ -2209,6 +2291,7 @@ process_implied_equality(PlannerInfo *root,
Expr *item2, Expr *item2,
Relids qualscope, Relids qualscope,
Relids nullable_relids, Relids nullable_relids,
Index security_level,
bool below_outer_join, bool below_outer_join,
bool both_const) bool both_const)
{ {
...@@ -2247,6 +2330,7 @@ process_implied_equality(PlannerInfo *root, ...@@ -2247,6 +2330,7 @@ process_implied_equality(PlannerInfo *root,
*/ */
distribute_qual_to_rels(root, (Node *) clause, distribute_qual_to_rels(root, (Node *) clause,
true, below_outer_join, JOIN_INNER, true, below_outer_join, JOIN_INNER,
security_level,
qualscope, NULL, NULL, nullable_relids, qualscope, NULL, NULL, nullable_relids,
NULL); NULL);
} }
...@@ -2270,7 +2354,8 @@ build_implied_join_equality(Oid opno, ...@@ -2270,7 +2354,8 @@ build_implied_join_equality(Oid opno,
Expr *item1, Expr *item1,
Expr *item2, Expr *item2,
Relids qualscope, Relids qualscope,
Relids nullable_relids) Relids nullable_relids,
Index security_level)
{ {
RestrictInfo *restrictinfo; RestrictInfo *restrictinfo;
Expr *clause; Expr *clause;
...@@ -2294,6 +2379,7 @@ build_implied_join_equality(Oid opno, ...@@ -2294,6 +2379,7 @@ build_implied_join_equality(Oid opno,
true, /* is_pushed_down */ true, /* is_pushed_down */
false, /* outerjoin_delayed */ false, /* outerjoin_delayed */
false, /* pseudoconstant */ false, /* pseudoconstant */
security_level, /* security_level */
qualscope, /* required_relids */ qualscope, /* required_relids */
NULL, /* outer_relids */ NULL, /* outer_relids */
nullable_relids); /* nullable_relids */ nullable_relids); /* nullable_relids */
......
...@@ -490,6 +490,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, ...@@ -490,6 +490,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
root->processed_tlist = NIL; root->processed_tlist = NIL;
root->grouping_map = NULL; root->grouping_map = NULL;
root->minmax_aggs = NIL; root->minmax_aggs = NIL;
root->qual_security_level = 0;
root->hasInheritedTarget = false; root->hasInheritedTarget = false;
root->hasRecursion = hasRecursion; root->hasRecursion = hasRecursion;
if (hasRecursion) if (hasRecursion)
...@@ -669,6 +670,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, ...@@ -669,6 +670,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
{ {
RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
int kind; int kind;
ListCell *lcsq;
if (rte->rtekind == RTE_RELATION) if (rte->rtekind == RTE_RELATION)
{ {
...@@ -704,6 +706,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse, ...@@ -704,6 +706,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
rte->values_lists = (List *) rte->values_lists = (List *)
preprocess_expression(root, (Node *) rte->values_lists, kind); preprocess_expression(root, (Node *) rte->values_lists, kind);
} }
/*
* Process each element of the securityQuals list as if it were a
* separate qual expression (as indeed it is). We need to do it this
* way to get proper canonicalization of AND/OR structure. Note that
* this converts each element into an implicit-AND sublist.
*/
foreach(lcsq, rte->securityQuals)
{
lfirst(lcsq) = preprocess_expression(root,
(Node *) lfirst(lcsq),
EXPRKIND_QUAL);
}
} }
/* /*
...@@ -978,7 +993,6 @@ inheritance_planner(PlannerInfo *root) ...@@ -978,7 +993,6 @@ inheritance_planner(PlannerInfo *root)
{ {
Query *parse = root->parse; Query *parse = root->parse;
int parentRTindex = parse->resultRelation; int parentRTindex = parse->resultRelation;
Bitmapset *resultRTindexes;
Bitmapset *subqueryRTindexes; Bitmapset *subqueryRTindexes;
Bitmapset *modifiableARIindexes; Bitmapset *modifiableARIindexes;
int nominalRelation = -1; int nominalRelation = -1;
...@@ -1012,26 +1026,7 @@ inheritance_planner(PlannerInfo *root) ...@@ -1012,26 +1026,7 @@ inheritance_planner(PlannerInfo *root)
* at least O(N^3) work expended here; and (2) would greatly complicate * at least O(N^3) work expended here; and (2) would greatly complicate
* management of the rowMarks list. * management of the rowMarks list.
* *
* Note that any RTEs with security barrier quals will be turned into * To begin with, generate a bitmapset of the relids of the subquery RTEs.
* subqueries during planning, and so we must create copies of them too,
* except where they are target relations, which will each only be used in
* a single plan.
*
* To begin with, we'll need a bitmapset of the target relation relids.
*/
resultRTindexes = bms_make_singleton(parentRTindex);
foreach(lc, root->append_rel_list)
{
AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
if (appinfo->parent_relid == parentRTindex)
resultRTindexes = bms_add_member(resultRTindexes,
appinfo->child_relid);
}
/*
* Now, generate a bitmapset of the relids of the subquery RTEs, including
* security-barrier RTEs that will become subqueries, as just explained.
*/ */
subqueryRTindexes = NULL; subqueryRTindexes = NULL;
rti = 1; rti = 1;
...@@ -1039,9 +1034,7 @@ inheritance_planner(PlannerInfo *root) ...@@ -1039,9 +1034,7 @@ inheritance_planner(PlannerInfo *root)
{ {
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
if (rte->rtekind == RTE_SUBQUERY || if (rte->rtekind == RTE_SUBQUERY)
(rte->securityQuals != NIL &&
!bms_is_member(rti, resultRTindexes)))
subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); subqueryRTindexes = bms_add_member(subqueryRTindexes, rti);
rti++; rti++;
} }
...@@ -1079,6 +1072,8 @@ inheritance_planner(PlannerInfo *root) ...@@ -1079,6 +1072,8 @@ inheritance_planner(PlannerInfo *root)
{ {
AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
PlannerInfo *subroot; PlannerInfo *subroot;
RangeTblEntry *parent_rte;
RangeTblEntry *child_rte;
RelOptInfo *sub_final_rel; RelOptInfo *sub_final_rel;
Path *subpath; Path *subpath;
...@@ -1104,6 +1099,15 @@ inheritance_planner(PlannerInfo *root) ...@@ -1104,6 +1099,15 @@ inheritance_planner(PlannerInfo *root)
(Node *) parse, (Node *) parse,
appinfo); appinfo);
/*
* If there are securityQuals attached to the parent, move them to the
* child rel (they've already been transformed properly for that).
*/
parent_rte = rt_fetch(parentRTindex, subroot->parse->rtable);
child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable);
child_rte->securityQuals = parent_rte->securityQuals;
parent_rte->securityQuals = NIL;
/* /*
* The rowMarks list might contain references to subquery RTEs, so * The rowMarks list might contain references to subquery RTEs, so
* make a copy that we can apply ChangeVarNodes to. (Fortunately, the * make a copy that we can apply ChangeVarNodes to. (Fortunately, the
...@@ -1151,11 +1155,11 @@ inheritance_planner(PlannerInfo *root) ...@@ -1151,11 +1155,11 @@ inheritance_planner(PlannerInfo *root)
/* /*
* If this isn't the first child Query, generate duplicates of all * If this isn't the first child Query, generate duplicates of all
* subquery (or subquery-to-be) RTEs, and adjust Var numbering to * subquery RTEs, and adjust Var numbering to reference the
* reference the duplicates. To simplify the loop logic, we scan the * duplicates. To simplify the loop logic, we scan the original rtable
* original rtable not the copy just made by adjust_appendrel_attrs; * not the copy just made by adjust_appendrel_attrs; that should be OK
* that should be OK since subquery RTEs couldn't contain any * since subquery RTEs couldn't contain any references to the target
* references to the target rel. * rel.
*/ */
if (final_rtable != NIL && subqueryRTindexes != NULL) if (final_rtable != NIL && subqueryRTindexes != NULL)
{ {
...@@ -1172,9 +1176,9 @@ inheritance_planner(PlannerInfo *root) ...@@ -1172,9 +1176,9 @@ inheritance_planner(PlannerInfo *root)
/* /*
* The RTE can't contain any references to its own RT * The RTE can't contain any references to its own RT
* index, except in the security barrier quals, so we can * index, except in its securityQuals, so we can save a
* save a few cycles by applying ChangeVarNodes before we * few cycles by applying ChangeVarNodes to the rest of
* append the RTE to the rangetable. * the rangetable before we append the RTE to it.
*/ */
newrti = list_length(subroot->parse->rtable) + 1; newrti = list_length(subroot->parse->rtable) + 1;
ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0);
...@@ -1212,12 +1216,6 @@ inheritance_planner(PlannerInfo *root) ...@@ -1212,12 +1216,6 @@ inheritance_planner(PlannerInfo *root)
/* Generate Path(s) for accessing this result relation */ /* Generate Path(s) for accessing this result relation */
grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ );
/*
* Planning may have modified the query result relation (if there were
* security barrier quals on the result RTE).
*/
appinfo->child_relid = subroot->parse->resultRelation;
/* /*
* We'll use the first child relation (even if it's excluded) as the * We'll use the first child relation (even if it's excluded) as the
* nominal target relation of the ModifyTable node. Because of the * nominal target relation of the ModifyTable node. Because of the
...@@ -1256,41 +1254,9 @@ inheritance_planner(PlannerInfo *root) ...@@ -1256,41 +1254,9 @@ inheritance_planner(PlannerInfo *root)
if (final_rtable == NIL) if (final_rtable == NIL)
final_rtable = subroot->parse->rtable; final_rtable = subroot->parse->rtable;
else else
{ final_rtable = list_concat(final_rtable,
List *tmp_rtable = NIL;
ListCell *cell1,
*cell2;
/*
* Check to see if any of the original RTEs were turned into
* subqueries during planning. Currently, this should only ever
* happen due to securityQuals being involved which push a
* relation down under a subquery, to ensure that the security
* barrier quals are evaluated first.
*
* When this happens, we want to use the new subqueries in the
* final rtable.
*/
forboth(cell1, final_rtable, cell2, subroot->parse->rtable)
{
RangeTblEntry *rte1 = (RangeTblEntry *) lfirst(cell1);
RangeTblEntry *rte2 = (RangeTblEntry *) lfirst(cell2);
if (rte1->rtekind == RTE_RELATION &&
rte2->rtekind == RTE_SUBQUERY)
{
/* Should only be when there are securityQuals today */
Assert(rte1->securityQuals != NIL);
tmp_rtable = lappend(tmp_rtable, rte2);
}
else
tmp_rtable = lappend(tmp_rtable, rte1);
}
final_rtable = list_concat(tmp_rtable,
list_copy_tail(subroot->parse->rtable, list_copy_tail(subroot->parse->rtable,
list_length(final_rtable))); list_length(final_rtable)));
}
/* /*
* We need to collect all the RelOptInfos from all child plans into * We need to collect all the RelOptInfos from all child plans into
...@@ -1634,12 +1600,6 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, ...@@ -1634,12 +1600,6 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
parse->resultRelation, parse->resultRelation,
parse->rtable); parse->rtable);
/*
* Expand any rangetable entries that have security barrier quals.
* This may add new security barrier subquery RTEs to the rangetable.
*/
expand_security_quals(root, tlist);
/* /*
* We are now done hacking up the query's targetlist. Most of the * We are now done hacking up the query's targetlist. Most of the
* remaining planning work will be done with the PathTarget * remaining planning work will be done with the PathTarget
...@@ -2297,17 +2257,8 @@ select_rowmark_type(RangeTblEntry *rte, LockClauseStrength strength) ...@@ -2297,17 +2257,8 @@ select_rowmark_type(RangeTblEntry *rte, LockClauseStrength strength)
/* /*
* We don't need a tuple lock, only the ability to re-fetch * We don't need a tuple lock, only the ability to re-fetch
* the row. Regular tables support ROW_MARK_REFERENCE, but if * the row.
* this RTE has security barrier quals, it will be turned into
* a subquery during planning, so use ROW_MARK_COPY.
*
* This is only necessary for LCS_NONE, since real tuple locks
* on an RTE with security barrier quals are supported by
* pushing the lock down into the subquery --- see
* expand_security_qual.
*/ */
if (rte->securityQuals != NIL)
return ROW_MARK_COPY;
return ROW_MARK_REFERENCE; return ROW_MARK_REFERENCE;
break; break;
case LCS_FORKEYSHARE: case LCS_FORKEYSHARE:
......
...@@ -12,6 +12,6 @@ subdir = src/backend/optimizer/prep ...@@ -12,6 +12,6 @@ subdir = src/backend/optimizer/prep
top_builddir = ../../../.. top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global include $(top_builddir)/src/Makefile.global
OBJS = prepjointree.o prepqual.o prepsecurity.o preptlist.o prepunion.o OBJS = prepjointree.o prepqual.o preptlist.o prepunion.o
include $(top_srcdir)/src/backend/common.mk include $(top_srcdir)/src/backend/common.mk
...@@ -913,6 +913,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, ...@@ -913,6 +913,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
subroot->processed_tlist = NIL; subroot->processed_tlist = NIL;
subroot->grouping_map = NULL; subroot->grouping_map = NULL;
subroot->minmax_aggs = NIL; subroot->minmax_aggs = NIL;
subroot->qual_security_level = 0;
subroot->hasInheritedTarget = false; subroot->hasInheritedTarget = false;
subroot->hasRecursion = false; subroot->hasRecursion = false;
subroot->wt_param_id = -1; subroot->wt_param_id = -1;
......
This diff is collapsed.
...@@ -56,7 +56,6 @@ typedef struct ...@@ -56,7 +56,6 @@ typedef struct
{ {
PlannerInfo *root; PlannerInfo *root;
AppendRelInfo *appinfo; AppendRelInfo *appinfo;
int sublevels_up;
} adjust_appendrel_attrs_context; } adjust_appendrel_attrs_context;
static Path *recurse_set_operations(Node *setOp, PlannerInfo *root, static Path *recurse_set_operations(Node *setOp, PlannerInfo *root,
...@@ -1467,12 +1466,19 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) ...@@ -1467,12 +1466,19 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
* We copy most fields of the parent's RTE, but replace relation OID * We copy most fields of the parent's RTE, but replace relation OID
* and relkind, and set inh = false. Also, set requiredPerms to zero * and relkind, and set inh = false. Also, set requiredPerms to zero
* since all required permissions checks are done on the original RTE. * since all required permissions checks are done on the original RTE.
* Likewise, set the child's securityQuals to empty, because we only
* want to apply the parent's RLS conditions regardless of what RLS
* properties individual children may have. (This is an intentional
* choice to make inherited RLS work like regular permissions checks.)
* The parent securityQuals will be propagated to children along with
* other base restriction clauses, so we don't need to do it here.
*/ */
childrte = copyObject(rte); childrte = copyObject(rte);
childrte->relid = childOID; childrte->relid = childOID;
childrte->relkind = newrelation->rd_rel->relkind; childrte->relkind = newrelation->rd_rel->relkind;
childrte->inh = false; childrte->inh = false;
childrte->requiredPerms = 0; childrte->requiredPerms = 0;
childrte->securityQuals = NIL;
parse->rtable = lappend(parse->rtable, childrte); parse->rtable = lappend(parse->rtable, childrte);
childRTindex = list_length(parse->rtable); childRTindex = list_length(parse->rtable);
...@@ -1541,7 +1547,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) ...@@ -1541,7 +1547,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
/* /*
* If all the children were temp tables, pretend it's a non-inheritance * If all the children were temp tables, pretend it's a non-inheritance
* situation. The duplicate RTE we added for the parent table is * situation. The duplicate RTE we added for the parent table is
* harmless, so we don't bother to get rid of it. * harmless, so we don't bother to get rid of it; ditto for the useless
* PlanRowMark node.
*/ */
if (list_length(appinfos) < 2) if (list_length(appinfos) < 2)
{ {
...@@ -1717,9 +1724,8 @@ translate_col_privs(const Bitmapset *parent_privs, ...@@ -1717,9 +1724,8 @@ translate_col_privs(const Bitmapset *parent_privs,
* child rel instead. We also update rtindexes appearing outside Vars, * child rel instead. We also update rtindexes appearing outside Vars,
* such as resultRelation and jointree relids. * such as resultRelation and jointree relids.
* *
* Note: this is applied after conversion of sublinks to subplans in the * Note: this is only applied after conversion of sublinks to subplans,
* query jointree, but there may still be sublinks in the security barrier * so we don't need to cope with recursion into sub-queries.
* quals of RTEs, so we do need to cope with recursion into sub-queries.
* *
* Note: this is not hugely different from what pullup_replace_vars() does; * Note: this is not hugely different from what pullup_replace_vars() does;
* maybe we should try to fold the two routines together. * maybe we should try to fold the two routines together.
...@@ -1732,12 +1738,9 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo) ...@@ -1732,12 +1738,9 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
context.root = root; context.root = root;
context.appinfo = appinfo; context.appinfo = appinfo;
context.sublevels_up = 0;
/* /*
* Must be prepared to start with a Query or a bare expression tree; if * Must be prepared to start with a Query or a bare expression tree.
* it's a Query, go straight to query_tree_walker to make sure that
* sublevels_up doesn't get incremented prematurely.
*/ */
if (node && IsA(node, Query)) if (node && IsA(node, Query))
{ {
...@@ -1776,7 +1779,7 @@ adjust_appendrel_attrs_mutator(Node *node, ...@@ -1776,7 +1779,7 @@ adjust_appendrel_attrs_mutator(Node *node,
{ {
Var *var = (Var *) copyObject(node); Var *var = (Var *) copyObject(node);
if (var->varlevelsup == context->sublevels_up && if (var->varlevelsup == 0 &&
var->varno == appinfo->parent_relid) var->varno == appinfo->parent_relid)
{ {
var->varno = appinfo->child_relid; var->varno = appinfo->child_relid;
...@@ -1793,7 +1796,6 @@ adjust_appendrel_attrs_mutator(Node *node, ...@@ -1793,7 +1796,6 @@ adjust_appendrel_attrs_mutator(Node *node,
if (newnode == NULL) if (newnode == NULL)
elog(ERROR, "attribute %d of relation \"%s\" does not exist", elog(ERROR, "attribute %d of relation \"%s\" does not exist",
var->varattno, get_rel_name(appinfo->parent_reloid)); var->varattno, get_rel_name(appinfo->parent_reloid));
((Var *) newnode)->varlevelsup += context->sublevels_up;
return newnode; return newnode;
} }
else if (var->varattno == 0) else if (var->varattno == 0)
...@@ -1836,17 +1838,10 @@ adjust_appendrel_attrs_mutator(Node *node, ...@@ -1836,17 +1838,10 @@ adjust_appendrel_attrs_mutator(Node *node,
RowExpr *rowexpr; RowExpr *rowexpr;
List *fields; List *fields;
RangeTblEntry *rte; RangeTblEntry *rte;
ListCell *lc;
rte = rt_fetch(appinfo->parent_relid, rte = rt_fetch(appinfo->parent_relid,
context->root->parse->rtable); context->root->parse->rtable);
fields = (List *) copyObject(appinfo->translated_vars); fields = (List *) copyObject(appinfo->translated_vars);
foreach(lc, fields)
{
Var *field = (Var *) lfirst(lc);
field->varlevelsup += context->sublevels_up;
}
rowexpr = makeNode(RowExpr); rowexpr = makeNode(RowExpr);
rowexpr->args = fields; rowexpr->args = fields;
rowexpr->row_typeid = var->vartype; rowexpr->row_typeid = var->vartype;
...@@ -1865,8 +1860,7 @@ adjust_appendrel_attrs_mutator(Node *node, ...@@ -1865,8 +1860,7 @@ adjust_appendrel_attrs_mutator(Node *node,
{ {
CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
if (context->sublevels_up == 0 && if (cexpr->cvarno == appinfo->parent_relid)
cexpr->cvarno == appinfo->parent_relid)
cexpr->cvarno = appinfo->child_relid; cexpr->cvarno = appinfo->child_relid;
return (Node *) cexpr; return (Node *) cexpr;
} }
...@@ -1874,8 +1868,7 @@ adjust_appendrel_attrs_mutator(Node *node, ...@@ -1874,8 +1868,7 @@ adjust_appendrel_attrs_mutator(Node *node,
{ {
RangeTblRef *rtr = (RangeTblRef *) copyObject(node); RangeTblRef *rtr = (RangeTblRef *) copyObject(node);
if (context->sublevels_up == 0 && if (rtr->rtindex == appinfo->parent_relid)
rtr->rtindex == appinfo->parent_relid)
rtr->rtindex = appinfo->child_relid; rtr->rtindex = appinfo->child_relid;
return (Node *) rtr; return (Node *) rtr;
} }
...@@ -1888,8 +1881,7 @@ adjust_appendrel_attrs_mutator(Node *node, ...@@ -1888,8 +1881,7 @@ adjust_appendrel_attrs_mutator(Node *node,
adjust_appendrel_attrs_mutator, adjust_appendrel_attrs_mutator,
(void *) context); (void *) context);
/* now fix JoinExpr's rtindex (probably never happens) */ /* now fix JoinExpr's rtindex (probably never happens) */
if (context->sublevels_up == 0 && if (j->rtindex == appinfo->parent_relid)
j->rtindex == appinfo->parent_relid)
j->rtindex = appinfo->child_relid; j->rtindex = appinfo->child_relid;
return (Node *) j; return (Node *) j;
} }
...@@ -1902,7 +1894,7 @@ adjust_appendrel_attrs_mutator(Node *node, ...@@ -1902,7 +1894,7 @@ adjust_appendrel_attrs_mutator(Node *node,
adjust_appendrel_attrs_mutator, adjust_appendrel_attrs_mutator,
(void *) context); (void *) context);
/* now fix PlaceHolderVar's relid sets */ /* now fix PlaceHolderVar's relid sets */
if (phv->phlevelsup == context->sublevels_up) if (phv->phlevelsup == 0)
phv->phrels = adjust_relid_set(phv->phrels, phv->phrels = adjust_relid_set(phv->phrels,
appinfo->parent_relid, appinfo->parent_relid,
appinfo->child_relid); appinfo->child_relid);
...@@ -1973,29 +1965,12 @@ adjust_appendrel_attrs_mutator(Node *node, ...@@ -1973,29 +1965,12 @@ adjust_appendrel_attrs_mutator(Node *node,
return (Node *) newinfo; return (Node *) newinfo;
} }
if (IsA(node, Query)) /*
{ * NOTE: we do not need to recurse into sublinks, because they should
/* * already have been converted to subplans before we see them.
* Recurse into sublink subqueries. This should only be possible in */
* security barrier quals of top-level RTEs. All other sublinks should Assert(!IsA(node, SubLink));
* have already been converted to subplans during expression Assert(!IsA(node, Query));
* preprocessing, but this doesn't happen for security barrier quals,
* since they are destined to become quals of a subquery RTE, which
* will be recursively planned, and so should not be preprocessed at
* this stage.
*
* We don't explicitly Assert() for securityQuals here simply because
* it's not trivial to do so.
*/
Query *newnode;
context->sublevels_up++;
newnode = query_tree_mutator((Query *) node,
adjust_appendrel_attrs_mutator,
(void *) context, 0);
context->sublevels_up--;
return (Node *) newnode;
}
return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, return expression_tree_mutator(node, adjust_appendrel_attrs_mutator,
(void *) context); (void *) context);
......
...@@ -1500,10 +1500,8 @@ contain_context_dependent_node_walker(Node *node, int *flags) ...@@ -1500,10 +1500,8 @@ contain_context_dependent_node_walker(Node *node, int *flags)
* *
* Returns true if the clause contains any non-leakproof functions that are * Returns true if the clause contains any non-leakproof functions that are
* passed Var nodes of the current query level, and which might therefore leak * passed Var nodes of the current query level, and which might therefore leak
* data. Qualifiers from outside a security_barrier view that might leak data * data. Such clauses must be applied after any lower-level security barrier
* in this way should not be pushed down into the view in case the contents of * clauses.
* tuples intended to be filtered out by the view are revealed by the leaky
* functions.
*/ */
bool bool
contain_leaked_vars(Node *clause) contain_leaked_vars(Node *clause)
...@@ -1598,10 +1596,10 @@ contain_leaked_vars_walker(Node *node, void *context) ...@@ -1598,10 +1596,10 @@ contain_leaked_vars_walker(Node *node, void *context)
case T_CurrentOfExpr: case T_CurrentOfExpr:
/* /*
* WHERE CURRENT OF doesn't contain function calls. Moreover, it * WHERE CURRENT OF doesn't contain leaky function calls.
* is important that this can be pushed down into a * Moreover, it is essential that this is considered non-leaky,
* security_barrier view, since the planner must always generate a * since the planner must always generate a TID scan when CURRENT
* TID scan when CURRENT OF is present -- c.f. cost_tidscan. * OF is present -- c.f. cost_tidscan.
*/ */
return false; return false;
......
...@@ -270,6 +270,7 @@ consider_new_or_clause(PlannerInfo *root, RelOptInfo *rel, ...@@ -270,6 +270,7 @@ consider_new_or_clause(PlannerInfo *root, RelOptInfo *rel,
true, true,
false, false,
false, false,
join_or_rinfo->security_level,
NULL, NULL,
NULL, NULL,
NULL); NULL);
...@@ -296,6 +297,8 @@ consider_new_or_clause(PlannerInfo *root, RelOptInfo *rel, ...@@ -296,6 +297,8 @@ consider_new_or_clause(PlannerInfo *root, RelOptInfo *rel,
* OK, add it to the rel's restriction-clause list. * OK, add it to the rel's restriction-clause list.
*/ */
rel->baserestrictinfo = lappend(rel->baserestrictinfo, or_rinfo); rel->baserestrictinfo = lappend(rel->baserestrictinfo, or_rinfo);
rel->baserestrict_min_security = Min(rel->baserestrict_min_security,
or_rinfo->security_level);
/* /*
* Adjust the original join OR clause's cached selectivity to compensate * Adjust the original join OR clause's cached selectivity to compensate
......
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
*/ */
#include "postgres.h" #include "postgres.h"
#include <limits.h>
#include "miscadmin.h" #include "miscadmin.h"
#include "optimizer/clauses.h" #include "optimizer/clauses.h"
#include "optimizer/cost.h" #include "optimizer/cost.h"
...@@ -135,6 +137,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) ...@@ -135,6 +137,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
rel->baserestrictinfo = NIL; rel->baserestrictinfo = NIL;
rel->baserestrictcost.startup = 0; rel->baserestrictcost.startup = 0;
rel->baserestrictcost.per_tuple = 0; rel->baserestrictcost.per_tuple = 0;
rel->baserestrict_min_security = UINT_MAX;
rel->joininfo = NIL; rel->joininfo = NIL;
rel->has_eclass_joins = false; rel->has_eclass_joins = false;
...@@ -172,6 +175,16 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) ...@@ -172,6 +175,16 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
/* Save the finished struct in the query's simple_rel_array */ /* Save the finished struct in the query's simple_rel_array */
root->simple_rel_array[relid] = rel; root->simple_rel_array[relid] = rel;
/*
* This is a convenient spot at which to note whether rels participating
* in the query have any securityQuals attached. If so, increase
* root->qual_security_level to ensure it's larger than the maximum
* security level needed for securityQuals.
*/
if (rte->securityQuals)
root->qual_security_level = Max(root->qual_security_level,
list_length(rte->securityQuals));
/* /*
* If this rel is an appendrel parent, recurse to build "other rel" * If this rel is an appendrel parent, recurse to build "other rel"
* RelOptInfos for its children. They are "other rels" because they are * RelOptInfos for its children. They are "other rels" because they are
...@@ -407,6 +420,7 @@ build_join_rel(PlannerInfo *root, ...@@ -407,6 +420,7 @@ build_join_rel(PlannerInfo *root,
joinrel->baserestrictinfo = NIL; joinrel->baserestrictinfo = NIL;
joinrel->baserestrictcost.startup = 0; joinrel->baserestrictcost.startup = 0;
joinrel->baserestrictcost.per_tuple = 0; joinrel->baserestrictcost.per_tuple = 0;
joinrel->baserestrict_min_security = UINT_MAX;
joinrel->joininfo = NIL; joinrel->joininfo = NIL;
joinrel->has_eclass_joins = false; joinrel->has_eclass_joins = false;
......
...@@ -24,6 +24,7 @@ static RestrictInfo *make_restrictinfo_internal(Expr *clause, ...@@ -24,6 +24,7 @@ static RestrictInfo *make_restrictinfo_internal(Expr *clause,
bool is_pushed_down, bool is_pushed_down,
bool outerjoin_delayed, bool outerjoin_delayed,
bool pseudoconstant, bool pseudoconstant,
Index security_level,
Relids required_relids, Relids required_relids,
Relids outer_relids, Relids outer_relids,
Relids nullable_relids); Relids nullable_relids);
...@@ -31,6 +32,7 @@ static Expr *make_sub_restrictinfos(Expr *clause, ...@@ -31,6 +32,7 @@ static Expr *make_sub_restrictinfos(Expr *clause,
bool is_pushed_down, bool is_pushed_down,
bool outerjoin_delayed, bool outerjoin_delayed,
bool pseudoconstant, bool pseudoconstant,
Index security_level,
Relids required_relids, Relids required_relids,
Relids outer_relids, Relids outer_relids,
Relids nullable_relids); Relids nullable_relids);
...@@ -43,7 +45,7 @@ static Expr *make_sub_restrictinfos(Expr *clause, ...@@ -43,7 +45,7 @@ static Expr *make_sub_restrictinfos(Expr *clause,
* *
* The is_pushed_down, outerjoin_delayed, and pseudoconstant flags for the * The is_pushed_down, outerjoin_delayed, and pseudoconstant flags for the
* RestrictInfo must be supplied by the caller, as well as the correct values * RestrictInfo must be supplied by the caller, as well as the correct values
* for outer_relids and nullable_relids. * for security_level, outer_relids, and nullable_relids.
* required_relids can be NULL, in which case it defaults to the actual clause * required_relids can be NULL, in which case it defaults to the actual clause
* contents (i.e., clause_relids). * contents (i.e., clause_relids).
* *
...@@ -56,6 +58,7 @@ make_restrictinfo(Expr *clause, ...@@ -56,6 +58,7 @@ make_restrictinfo(Expr *clause,
bool is_pushed_down, bool is_pushed_down,
bool outerjoin_delayed, bool outerjoin_delayed,
bool pseudoconstant, bool pseudoconstant,
Index security_level,
Relids required_relids, Relids required_relids,
Relids outer_relids, Relids outer_relids,
Relids nullable_relids) Relids nullable_relids)
...@@ -69,6 +72,7 @@ make_restrictinfo(Expr *clause, ...@@ -69,6 +72,7 @@ make_restrictinfo(Expr *clause,
is_pushed_down, is_pushed_down,
outerjoin_delayed, outerjoin_delayed,
pseudoconstant, pseudoconstant,
security_level,
required_relids, required_relids,
outer_relids, outer_relids,
nullable_relids); nullable_relids);
...@@ -81,64 +85,12 @@ make_restrictinfo(Expr *clause, ...@@ -81,64 +85,12 @@ make_restrictinfo(Expr *clause,
is_pushed_down, is_pushed_down,
outerjoin_delayed, outerjoin_delayed,
pseudoconstant, pseudoconstant,
security_level,
required_relids, required_relids,
outer_relids, outer_relids,
nullable_relids); nullable_relids);
} }
/*
* make_restrictinfos_from_actual_clauses
*
* Given a list of implicitly-ANDed restriction clauses, produce a list
* of RestrictInfo nodes. This is used to reconstitute the RestrictInfo
* representation after doing transformations of a list of clauses.
*
* We assume that the clauses are relation-level restrictions and therefore
* we don't have to worry about is_pushed_down, outerjoin_delayed,
* outer_relids, and nullable_relids (these can be assumed true, false,
* NULL, and NULL, respectively).
* We do take care to recognize pseudoconstant clauses properly.
*/
List *
make_restrictinfos_from_actual_clauses(PlannerInfo *root,
List *clause_list)
{
List *result = NIL;
ListCell *l;
foreach(l, clause_list)
{
Expr *clause = (Expr *) lfirst(l);
bool pseudoconstant;
RestrictInfo *rinfo;
/*
* It's pseudoconstant if it contains no Vars and no volatile
* functions. We probably can't see any sublinks here, so
* contain_var_clause() would likely be enough, but for safety use
* contain_vars_of_level() instead.
*/
pseudoconstant =
!contain_vars_of_level((Node *) clause, 0) &&
!contain_volatile_functions((Node *) clause);
if (pseudoconstant)
{
/* tell createplan.c to check for gating quals */
root->hasPseudoConstantQuals = true;
}
rinfo = make_restrictinfo(clause,
true,
false,
pseudoconstant,
NULL,
NULL,
NULL);
result = lappend(result, rinfo);
}
return result;
}
/* /*
* make_restrictinfo_internal * make_restrictinfo_internal
* *
...@@ -150,6 +102,7 @@ make_restrictinfo_internal(Expr *clause, ...@@ -150,6 +102,7 @@ make_restrictinfo_internal(Expr *clause,
bool is_pushed_down, bool is_pushed_down,
bool outerjoin_delayed, bool outerjoin_delayed,
bool pseudoconstant, bool pseudoconstant,
Index security_level,
Relids required_relids, Relids required_relids,
Relids outer_relids, Relids outer_relids,
Relids nullable_relids) Relids nullable_relids)
...@@ -162,9 +115,20 @@ make_restrictinfo_internal(Expr *clause, ...@@ -162,9 +115,20 @@ make_restrictinfo_internal(Expr *clause,
restrictinfo->outerjoin_delayed = outerjoin_delayed; restrictinfo->outerjoin_delayed = outerjoin_delayed;
restrictinfo->pseudoconstant = pseudoconstant; restrictinfo->pseudoconstant = pseudoconstant;
restrictinfo->can_join = false; /* may get set below */ restrictinfo->can_join = false; /* may get set below */
restrictinfo->security_level = security_level;
restrictinfo->outer_relids = outer_relids; restrictinfo->outer_relids = outer_relids;
restrictinfo->nullable_relids = nullable_relids; restrictinfo->nullable_relids = nullable_relids;
/*
* If it's potentially delayable by lower-level security quals, figure out
* whether it's leakproof. We can skip testing this for level-zero quals,
* since they would never get delayed on security grounds anyway.
*/
if (security_level > 0)
restrictinfo->leakproof = !contain_leaked_vars((Node *) clause);
else
restrictinfo->leakproof = false; /* really, "don't know" */
/* /*
* If it's a binary opclause, set up left/right relids info. In any case * If it's a binary opclause, set up left/right relids info. In any case
* set up the total clause relids info. * set up the total clause relids info.
...@@ -250,7 +214,7 @@ make_restrictinfo_internal(Expr *clause, ...@@ -250,7 +214,7 @@ make_restrictinfo_internal(Expr *clause,
* *
* The same is_pushed_down, outerjoin_delayed, and pseudoconstant flag * The same is_pushed_down, outerjoin_delayed, and pseudoconstant flag
* values can be applied to all RestrictInfo nodes in the result. Likewise * values can be applied to all RestrictInfo nodes in the result. Likewise
* for outer_relids and nullable_relids. * for security_level, outer_relids, and nullable_relids.
* *
* The given required_relids are attached to our top-level output, * The given required_relids are attached to our top-level output,
* but any OR-clause constituents are allowed to default to just the * but any OR-clause constituents are allowed to default to just the
...@@ -261,6 +225,7 @@ make_sub_restrictinfos(Expr *clause, ...@@ -261,6 +225,7 @@ make_sub_restrictinfos(Expr *clause,
bool is_pushed_down, bool is_pushed_down,
bool outerjoin_delayed, bool outerjoin_delayed,
bool pseudoconstant, bool pseudoconstant,
Index security_level,
Relids required_relids, Relids required_relids,
Relids outer_relids, Relids outer_relids,
Relids nullable_relids) Relids nullable_relids)
...@@ -276,6 +241,7 @@ make_sub_restrictinfos(Expr *clause, ...@@ -276,6 +241,7 @@ make_sub_restrictinfos(Expr *clause,
is_pushed_down, is_pushed_down,
outerjoin_delayed, outerjoin_delayed,
pseudoconstant, pseudoconstant,
security_level,
NULL, NULL,
outer_relids, outer_relids,
nullable_relids)); nullable_relids));
...@@ -284,6 +250,7 @@ make_sub_restrictinfos(Expr *clause, ...@@ -284,6 +250,7 @@ make_sub_restrictinfos(Expr *clause,
is_pushed_down, is_pushed_down,
outerjoin_delayed, outerjoin_delayed,
pseudoconstant, pseudoconstant,
security_level,
required_relids, required_relids,
outer_relids, outer_relids,
nullable_relids); nullable_relids);
...@@ -299,6 +266,7 @@ make_sub_restrictinfos(Expr *clause, ...@@ -299,6 +266,7 @@ make_sub_restrictinfos(Expr *clause,
is_pushed_down, is_pushed_down,
outerjoin_delayed, outerjoin_delayed,
pseudoconstant, pseudoconstant,
security_level,
required_relids, required_relids,
outer_relids, outer_relids,
nullable_relids)); nullable_relids));
...@@ -310,6 +278,7 @@ make_sub_restrictinfos(Expr *clause, ...@@ -310,6 +278,7 @@ make_sub_restrictinfos(Expr *clause,
is_pushed_down, is_pushed_down,
outerjoin_delayed, outerjoin_delayed,
pseudoconstant, pseudoconstant,
security_level,
required_relids, required_relids,
outer_relids, outer_relids,
nullable_relids); nullable_relids);
...@@ -330,42 +299,36 @@ restriction_is_or_clause(RestrictInfo *restrictinfo) ...@@ -330,42 +299,36 @@ restriction_is_or_clause(RestrictInfo *restrictinfo)
} }
/* /*
* get_actual_clauses * restriction_is_securely_promotable
* *
* Returns a list containing the bare clauses from 'restrictinfo_list'. * Returns true if it's okay to evaluate this clause "early", that is before
* * other restriction clauses attached to the specified relation.
* This is only to be used in cases where none of the RestrictInfos can
* be pseudoconstant clauses (for instance, it's OK on indexqual lists).
*/ */
List * bool
get_actual_clauses(List *restrictinfo_list) restriction_is_securely_promotable(RestrictInfo *restrictinfo,
RelOptInfo *rel)
{ {
List *result = NIL; /*
ListCell *l; * It's okay if there are no baserestrictinfo clauses for the rel that
* would need to go before this one, *or* if this one is leakproof.
foreach(l, restrictinfo_list) */
{ if (restrictinfo->security_level <= rel->baserestrict_min_security ||
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); restrictinfo->leakproof)
return true;
Assert(IsA(rinfo, RestrictInfo)); else
return false;
Assert(!rinfo->pseudoconstant);
result = lappend(result, rinfo->clause);
}
return result;
} }
/* /*
* get_all_actual_clauses * get_actual_clauses
* *
* Returns a list containing the bare clauses from 'restrictinfo_list'. * Returns a list containing the bare clauses from 'restrictinfo_list'.
* *
* This loses the distinction between regular and pseudoconstant clauses, * This is only to be used in cases where none of the RestrictInfos can
* so be careful what you use it for. * be pseudoconstant clauses (for instance, it's OK on indexqual lists).
*/ */
List * List *
get_all_actual_clauses(List *restrictinfo_list) get_actual_clauses(List *restrictinfo_list)
{ {
List *result = NIL; List *result = NIL;
ListCell *l; ListCell *l;
...@@ -376,6 +339,8 @@ get_all_actual_clauses(List *restrictinfo_list) ...@@ -376,6 +339,8 @@ get_all_actual_clauses(List *restrictinfo_list)
Assert(IsA(rinfo, RestrictInfo)); Assert(IsA(rinfo, RestrictInfo));
Assert(!rinfo->pseudoconstant);
result = lappend(result, rinfo->clause); result = lappend(result, rinfo->clause);
} }
return result; return result;
......
...@@ -286,6 +286,9 @@ typedef struct PlannerInfo ...@@ -286,6 +286,9 @@ typedef struct PlannerInfo
double tuple_fraction; /* tuple_fraction passed to query_planner */ double tuple_fraction; /* tuple_fraction passed to query_planner */
double limit_tuples; /* limit_tuples passed to query_planner */ double limit_tuples; /* limit_tuples passed to query_planner */
Index qual_security_level; /* minimum security_level for quals */
/* Note: qual_security_level is zero if there are no securityQuals */
bool hasInheritedTarget; /* true if parse->resultRelation is an bool hasInheritedTarget; /* true if parse->resultRelation is an
* inheritance child rel */ * inheritance child rel */
bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */
...@@ -443,6 +446,8 @@ typedef struct PlannerInfo ...@@ -443,6 +446,8 @@ typedef struct PlannerInfo
* participates (only used for base rels) * participates (only used for base rels)
* baserestrictcost - Estimated cost of evaluating the baserestrictinfo * baserestrictcost - Estimated cost of evaluating the baserestrictinfo
* clauses at a single tuple (only used for base rels) * clauses at a single tuple (only used for base rels)
* baserestrict_min_security - Smallest security_level found among
* clauses in baserestrictinfo
* joininfo - List of RestrictInfo nodes, containing info about each * joininfo - List of RestrictInfo nodes, containing info about each
* join clause in which this relation participates (but * join clause in which this relation participates (but
* note this excludes clauses that might be derivable from * note this excludes clauses that might be derivable from
...@@ -539,6 +544,8 @@ typedef struct RelOptInfo ...@@ -539,6 +544,8 @@ typedef struct RelOptInfo
List *baserestrictinfo; /* RestrictInfo structures (if base List *baserestrictinfo; /* RestrictInfo structures (if base
* rel) */ * rel) */
QualCost baserestrictcost; /* cost of evaluating the above */ QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
List *joininfo; /* RestrictInfo structures for join clauses List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */ * involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */ bool has_eclass_joins; /* T means joininfo is incomplete */
...@@ -713,6 +720,8 @@ typedef struct EquivalenceClass ...@@ -713,6 +720,8 @@ typedef struct EquivalenceClass
bool ec_below_outer_join; /* equivalence applies below an OJ */ bool ec_below_outer_join; /* equivalence applies below an OJ */
bool ec_broken; /* failed to generate needed clauses? */ bool ec_broken; /* failed to generate needed clauses? */
Index ec_sortref; /* originating sortclause label, or 0 */ Index ec_sortref; /* originating sortclause label, or 0 */
Index ec_min_security; /* minimum security_level in ec_sources */
Index ec_max_security; /* maximum security_level in ec_sources */
struct EquivalenceClass *ec_merged; /* set if merged into another EC */ struct EquivalenceClass *ec_merged; /* set if merged into another EC */
} EquivalenceClass; } EquivalenceClass;
...@@ -1560,6 +1569,15 @@ typedef struct LimitPath ...@@ -1560,6 +1569,15 @@ typedef struct LimitPath
* outer join(s). A clause that is not outerjoin_delayed can be enforced * outer join(s). A clause that is not outerjoin_delayed can be enforced
* anywhere it is computable. * anywhere it is computable.
* *
* To handle security-barrier conditions efficiently, we mark RestrictInfo
* nodes with a security_level field, in which higher values identify clauses
* coming from less-trusted sources. The exact semantics are that a clause
* cannot be evaluated before another clause with a lower security_level value
* unless the first clause is leakproof. As with outer-join clauses, this
* creates a reason for clauses to sometimes need to be evaluated higher in
* the join tree than their contents would suggest; and even at a single plan
* node, this rule constrains the order of application of clauses.
*
* In general, the referenced clause might be arbitrarily complex. The * In general, the referenced clause might be arbitrarily complex. The
* kinds of clauses we can handle as indexscan quals, mergejoin clauses, * kinds of clauses we can handle as indexscan quals, mergejoin clauses,
* or hashjoin clauses are limited (e.g., no volatile functions). The code * or hashjoin clauses are limited (e.g., no volatile functions). The code
...@@ -1614,6 +1632,10 @@ typedef struct RestrictInfo ...@@ -1614,6 +1632,10 @@ typedef struct RestrictInfo
bool pseudoconstant; /* see comment above */ bool pseudoconstant; /* see comment above */
bool leakproof; /* TRUE if known to contain no leaked Vars */
Index security_level; /* see comment above */
/* The set of relids (varnos) actually referenced in the clause: */ /* The set of relids (varnos) actually referenced in the clause: */
Relids clause_relids; Relids clause_relids;
......
...@@ -87,6 +87,7 @@ extern void process_implied_equality(PlannerInfo *root, ...@@ -87,6 +87,7 @@ extern void process_implied_equality(PlannerInfo *root,
Expr *item2, Expr *item2,
Relids qualscope, Relids qualscope,
Relids nullable_relids, Relids nullable_relids,
Index security_level,
bool below_outer_join, bool below_outer_join,
bool both_const); bool both_const);
extern RestrictInfo *build_implied_join_equality(Oid opno, extern RestrictInfo *build_implied_join_equality(Oid opno,
...@@ -94,7 +95,8 @@ extern RestrictInfo *build_implied_join_equality(Oid opno, ...@@ -94,7 +95,8 @@ extern RestrictInfo *build_implied_join_equality(Oid opno,
Expr *item1, Expr *item1,
Expr *item2, Expr *item2,
Relids qualscope, Relids qualscope,
Relids nullable_relids); Relids nullable_relids,
Index security_level);
extern void match_foreign_keys_to_quals(PlannerInfo *root); extern void match_foreign_keys_to_quals(PlannerInfo *root);
/* /*
......
...@@ -35,11 +35,6 @@ extern Relids get_relids_for_join(PlannerInfo *root, int joinrelid); ...@@ -35,11 +35,6 @@ extern Relids get_relids_for_join(PlannerInfo *root, int joinrelid);
extern Node *negate_clause(Node *node); extern Node *negate_clause(Node *node);
extern Expr *canonicalize_qual(Expr *qual); extern Expr *canonicalize_qual(Expr *qual);
/*
* prototypes for prepsecurity.c
*/
extern void expand_security_quals(PlannerInfo *root, List *tlist);
/* /*
* prototypes for preptlist.c * prototypes for preptlist.c
*/ */
......
...@@ -19,20 +19,20 @@ ...@@ -19,20 +19,20 @@
/* Convenience macro for the common case of a valid-everywhere qual */ /* Convenience macro for the common case of a valid-everywhere qual */
#define make_simple_restrictinfo(clause) \ #define make_simple_restrictinfo(clause) \
make_restrictinfo(clause, true, false, false, NULL, NULL, NULL) make_restrictinfo(clause, true, false, false, 0, NULL, NULL, NULL)
extern RestrictInfo *make_restrictinfo(Expr *clause, extern RestrictInfo *make_restrictinfo(Expr *clause,
bool is_pushed_down, bool is_pushed_down,
bool outerjoin_delayed, bool outerjoin_delayed,
bool pseudoconstant, bool pseudoconstant,
Index security_level,
Relids required_relids, Relids required_relids,
Relids outer_relids, Relids outer_relids,
Relids nullable_relids); Relids nullable_relids);
extern List *make_restrictinfos_from_actual_clauses(PlannerInfo *root,
List *clause_list);
extern bool restriction_is_or_clause(RestrictInfo *restrictinfo); extern bool restriction_is_or_clause(RestrictInfo *restrictinfo);
extern bool restriction_is_securely_promotable(RestrictInfo *restrictinfo,
RelOptInfo *rel);
extern List *get_actual_clauses(List *restrictinfo_list); extern List *get_actual_clauses(List *restrictinfo_list);
extern List *get_all_actual_clauses(List *restrictinfo_list);
extern List *extract_actual_clauses(List *restrictinfo_list, extern List *extract_actual_clauses(List *restrictinfo_list,
bool pseudoconstant); bool pseudoconstant);
extern void extract_actual_join_clauses(List *restrictinfo_list, extern void extract_actual_join_clauses(List *restrictinfo_list,
......
...@@ -145,13 +145,11 @@ ERROR: new row violates row-level security policy for table "rls_test_permissiv ...@@ -145,13 +145,11 @@ ERROR: new row violates row-level security policy for table "rls_test_permissiv
SET ROLE regress_s1; SET ROLE regress_s1;
-- With both internal and hook policies, restrictive -- With both internal and hook policies, restrictive
EXPLAIN (costs off) SELECT * FROM rls_test_restrictive; EXPLAIN (costs off) SELECT * FROM rls_test_restrictive;
QUERY PLAN QUERY PLAN
--------------------------------------------------------------- ------------------------------------------------------------------
Subquery Scan on rls_test_restrictive Seq Scan on rls_test_restrictive
Filter: ((rls_test_restrictive.data % 2) = 0) Filter: (("current_user"() = supervisor) AND ((data % 2) = 0))
-> Seq Scan on rls_test_restrictive rls_test_restrictive_1 (2 rows)
Filter: ("current_user"() = supervisor)
(4 rows)
SELECT * FROM rls_test_restrictive; SELECT * FROM rls_test_restrictive;
username | supervisor | data username | supervisor | data
...@@ -173,13 +171,11 @@ ERROR: new row violates row-level security policy for table "rls_test_restricti ...@@ -173,13 +171,11 @@ ERROR: new row violates row-level security policy for table "rls_test_restricti
-- With both internal and hook policies, both permissive -- With both internal and hook policies, both permissive
-- and restrictive hook policies -- and restrictive hook policies
EXPLAIN (costs off) SELECT * FROM rls_test_both; EXPLAIN (costs off) SELECT * FROM rls_test_both;
QUERY PLAN QUERY PLAN
------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------------
Subquery Scan on rls_test_both Seq Scan on rls_test_both
Filter: (((rls_test_both.data % 2) = 0) OR ("current_user"() = rls_test_both.username)) Filter: (("current_user"() = supervisor) AND (((data % 2) = 0) OR ("current_user"() = username)))
-> Seq Scan on rls_test_both rls_test_both_1 (2 rows)
Filter: ("current_user"() = supervisor)
(4 rows)
SELECT * FROM rls_test_both; SELECT * FROM rls_test_both;
username | supervisor | data username | supervisor | data
......
...@@ -381,3 +381,45 @@ explain (costs off) ...@@ -381,3 +381,45 @@ explain (costs off)
Index Cond: (ff = '42'::bigint) Index Cond: (ff = '42'::bigint)
(14 rows) (14 rows)
-- check effects of row-level security
set enable_nestloop = on;
set enable_mergejoin = off;
alter table ec1 enable row level security;
create policy p1 on ec1 using (f1 < '5'::int8alias1);
create user regress_user_ectest;
grant select on ec0 to regress_user_ectest;
grant select on ec1 to regress_user_ectest;
-- without any RLS, we'll treat {a.ff, b.ff, 43} as an EquivalenceClass
explain (costs off)
select * from ec0 a, ec1 b
where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
QUERY PLAN
---------------------------------------------
Nested Loop
-> Index Scan using ec0_pkey on ec0 a
Index Cond: (ff = '43'::int8alias1)
-> Index Scan using ec1_pkey on ec1 b
Index Cond: (ff = '43'::int8alias1)
(5 rows)
set session authorization regress_user_ectest;
-- with RLS active, the non-leakproof a.ff = 43 clause is not treated
-- as a suitable source for an EquivalenceClass; currently, this is true
-- even though the RLS clause has nothing to do directly with the EC
explain (costs off)
select * from ec0 a, ec1 b
where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
QUERY PLAN
---------------------------------------------
Nested Loop
-> Index Scan using ec0_pkey on ec0 a
Index Cond: (ff = '43'::int8alias1)
-> Index Scan using ec1_pkey on ec1 b
Index Cond: (ff = a.ff)
Filter: (f1 < '5'::int8alias1)
(6 rows)
reset session authorization;
revoke select on ec0 from regress_user_ectest;
revoke select on ec1 from regress_user_ectest;
drop user regress_user_ectest;
This diff is collapsed.
...@@ -222,3 +222,35 @@ explain (costs off) ...@@ -222,3 +222,35 @@ explain (costs off)
union all union all
select ff + 4 as x from ec1) as ss1 select ff + 4 as x from ec1) as ss1
where ss1.x = ec1.f1 and ec1.ff = 42::int8; where ss1.x = ec1.f1 and ec1.ff = 42::int8;
-- check effects of row-level security
set enable_nestloop = on;
set enable_mergejoin = off;
alter table ec1 enable row level security;
create policy p1 on ec1 using (f1 < '5'::int8alias1);
create user regress_user_ectest;
grant select on ec0 to regress_user_ectest;
grant select on ec1 to regress_user_ectest;
-- without any RLS, we'll treat {a.ff, b.ff, 43} as an EquivalenceClass
explain (costs off)
select * from ec0 a, ec1 b
where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
set session authorization regress_user_ectest;
-- with RLS active, the non-leakproof a.ff = 43 clause is not treated
-- as a suitable source for an EquivalenceClass; currently, this is true
-- even though the RLS clause has nothing to do directly with the EC
explain (costs off)
select * from ec0 a, ec1 b
where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
reset session authorization;
revoke select on ec0 from regress_user_ectest;
revoke select on ec1 from regress_user_ectest;
drop user regress_user_ectest;
...@@ -1001,8 +1001,8 @@ SELECT * FROM v1 WHERE a=3; -- should not see anything ...@@ -1001,8 +1001,8 @@ SELECT * FROM v1 WHERE a=3; -- should not see anything
SELECT * FROM v1 WHERE a=8; SELECT * FROM v1 WHERE a=8;
EXPLAIN (VERBOSE, COSTS OFF) EXPLAIN (VERBOSE, COSTS OFF)
UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a = 3; UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a = 3; UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100 SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100
SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100 SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment