diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2017-01-18 12:58:20 -0500 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2017-01-18 12:58:20 -0500 |
commit | 215b43cdc8d6b4a1700886a39df1ee735cb0274d (patch) | |
tree | 793e79c1b1444b09776e3b7d61c80e0244bab088 /src/backend/optimizer/plan/createplan.c | |
parent | aa17c06fb58533d09c79c68a4d34a6f56687ee38 (diff) | |
download | postgresql-215b43cdc8d6b4a1700886a39df1ee735cb0274d.tar.gz postgresql-215b43cdc8d6b4a1700886a39df1ee735cb0274d.zip |
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
Diffstat (limited to 'src/backend/optimizer/plan/createplan.c')
-rw-r--r-- | src/backend/optimizer/plan/createplan.c | 53 |
1 files changed, 45 insertions, 8 deletions
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index c7bcd9b84c8..c4ada214ed2 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -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 * 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 * selectivity, but it's not immediately clear how to account for both, * 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, - * being careful not to change the order when (as is often the case) the - * estimates are identical. + * would be doubtful anyway. So we just order by security level then + * estimated per-tuple cost, being careful not to change the order when + * (as is often the case) the estimates are identical. * * 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 - * 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 * 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 - * a cost/selectivity combination would likely do the wrong thing. + * instead of bare clauses. This is another reason why trying to consider + * selectivity in the ordering would likely do the wrong thing. */ static List * order_qual_clauses(PlannerInfo *root, List *clauses) @@ -4523,6 +4534,7 @@ order_qual_clauses(PlannerInfo *root, List *clauses) { Node *clause; Cost cost; + Index security_level; } QualItem; int nitems = list_length(clauses); QualItem *items; @@ -4548,6 +4560,27 @@ order_qual_clauses(PlannerInfo *root, List *clauses) cost_qual_eval_node(&qcost, clause, root); items[i].clause = clause; 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++; } @@ -4564,9 +4597,13 @@ order_qual_clauses(PlannerInfo *root, List *clauses) /* insert newitem into the already-sorted subarray */ 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; - items[j] = items[j - 1]; + items[j] = *olditem; } items[j] = newitem; } |