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/planner.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/planner.c')
-rw-r--r-- | src/backend/optimizer/plan/planner.c | 125 |
1 files changed, 38 insertions, 87 deletions
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index f936710171c..25f2c5a6147 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -490,6 +490,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->processed_tlist = NIL; root->grouping_map = NULL; root->minmax_aggs = NIL; + root->qual_security_level = 0; root->hasInheritedTarget = false; root->hasRecursion = hasRecursion; if (hasRecursion) @@ -669,6 +670,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, { RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); int kind; + ListCell *lcsq; if (rte->rtekind == RTE_RELATION) { @@ -704,6 +706,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rte->values_lists = (List *) 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) { Query *parse = root->parse; int parentRTindex = parse->resultRelation; - Bitmapset *resultRTindexes; Bitmapset *subqueryRTindexes; Bitmapset *modifiableARIindexes; int nominalRelation = -1; @@ -1012,26 +1026,7 @@ inheritance_planner(PlannerInfo *root) * at least O(N^3) work expended here; and (2) would greatly complicate * management of the rowMarks list. * - * Note that any RTEs with security barrier quals will be turned into - * 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. + * To begin with, generate a bitmapset of the relids of the subquery RTEs. */ subqueryRTindexes = NULL; rti = 1; @@ -1039,9 +1034,7 @@ inheritance_planner(PlannerInfo *root) { RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); - if (rte->rtekind == RTE_SUBQUERY || - (rte->securityQuals != NIL && - !bms_is_member(rti, resultRTindexes))) + if (rte->rtekind == RTE_SUBQUERY) subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); rti++; } @@ -1079,6 +1072,8 @@ inheritance_planner(PlannerInfo *root) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); PlannerInfo *subroot; + RangeTblEntry *parent_rte; + RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; @@ -1105,6 +1100,15 @@ inheritance_planner(PlannerInfo *root) 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 * make a copy that we can apply ChangeVarNodes to. (Fortunately, the * executor doesn't need to see the modified copies --- we can just @@ -1151,11 +1155,11 @@ inheritance_planner(PlannerInfo *root) /* * If this isn't the first child Query, generate duplicates of all - * subquery (or subquery-to-be) RTEs, and adjust Var numbering to - * reference the duplicates. To simplify the loop logic, we scan the - * original rtable not the copy just made by adjust_appendrel_attrs; - * that should be OK since subquery RTEs couldn't contain any - * references to the target rel. + * subquery RTEs, and adjust Var numbering to reference the + * duplicates. To simplify the loop logic, we scan the original rtable + * not the copy just made by adjust_appendrel_attrs; that should be OK + * since subquery RTEs couldn't contain any references to the target + * rel. */ if (final_rtable != NIL && subqueryRTindexes != NULL) { @@ -1172,9 +1176,9 @@ inheritance_planner(PlannerInfo *root) /* * The RTE can't contain any references to its own RT - * index, except in the security barrier quals, so we can - * save a few cycles by applying ChangeVarNodes before we - * append the RTE to the rangetable. + * index, except in its securityQuals, so we can save a + * few cycles by applying ChangeVarNodes to the rest of + * the rangetable before we append the RTE to it. */ newrti = list_length(subroot->parse->rtable) + 1; ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); @@ -1213,12 +1217,6 @@ inheritance_planner(PlannerInfo *root) 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 * nominal target relation of the ModifyTable node. Because of the * way expand_inherited_rtentry works, this should always be the RTE @@ -1256,41 +1254,9 @@ inheritance_planner(PlannerInfo *root) if (final_rtable == NIL) final_rtable = subroot->parse->rtable; else - { - 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, + final_rtable = list_concat(final_rtable, list_copy_tail(subroot->parse->rtable, list_length(final_rtable))); - } /* * We need to collect all the RelOptInfos from all child plans into @@ -1635,12 +1601,6 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, 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 * remaining planning work will be done with the PathTarget * representation of tlists, but save aside the full representation so @@ -2297,17 +2257,8 @@ select_rowmark_type(RangeTblEntry *rte, LockClauseStrength strength) /* * We don't need a tuple lock, only the ability to re-fetch - * the row. Regular tables support ROW_MARK_REFERENCE, but if - * 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. + * the row. */ - if (rte->securityQuals != NIL) - return ROW_MARK_COPY; return ROW_MARK_REFERENCE; break; case LCS_FORKEYSHARE: |