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/include | |
parent | aa17c06fb58533d09c79c68a4d34a6f56687ee38 (diff) |
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/include')
-rw-r--r-- | src/include/nodes/relation.h | 22 | ||||
-rw-r--r-- | src/include/optimizer/planmain.h | 4 | ||||
-rw-r--r-- | src/include/optimizer/prep.h | 5 | ||||
-rw-r--r-- | src/include/optimizer/restrictinfo.h | 8 |
4 files changed, 29 insertions, 10 deletions
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index e1d31c795a0..1e950c4afd3 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -286,6 +286,9 @@ typedef struct PlannerInfo double tuple_fraction; /* tuple_fraction 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 * inheritance child rel */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ @@ -443,6 +446,8 @@ typedef struct PlannerInfo * participates (only used for base rels) * baserestrictcost - Estimated cost of evaluating the baserestrictinfo * 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 * join clause in which this relation participates (but * note this excludes clauses that might be derivable from @@ -539,6 +544,8 @@ typedef struct RelOptInfo List *baserestrictinfo; /* RestrictInfo structures (if base * rel) */ QualCost baserestrictcost; /* cost of evaluating the above */ + Index baserestrict_min_security; /* min security_level found in + * baserestrictinfo */ List *joininfo; /* RestrictInfo structures for join clauses * involving this rel */ bool has_eclass_joins; /* T means joininfo is incomplete */ @@ -713,6 +720,8 @@ typedef struct EquivalenceClass bool ec_below_outer_join; /* equivalence applies below an OJ */ bool ec_broken; /* failed to generate needed clauses? */ 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 */ } EquivalenceClass; @@ -1560,6 +1569,15 @@ typedef struct LimitPath * outer join(s). A clause that is not outerjoin_delayed can be enforced * 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 * kinds of clauses we can handle as indexscan quals, mergejoin clauses, * or hashjoin clauses are limited (e.g., no volatile functions). The code @@ -1614,6 +1632,10 @@ typedef struct RestrictInfo 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: */ Relids clause_relids; diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 8468b0c47f5..94ef84bca9c 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -87,6 +87,7 @@ extern void process_implied_equality(PlannerInfo *root, Expr *item2, Relids qualscope, Relids nullable_relids, + Index security_level, bool below_outer_join, bool both_const); extern RestrictInfo *build_implied_join_equality(Oid opno, @@ -94,7 +95,8 @@ extern RestrictInfo *build_implied_join_equality(Oid opno, Expr *item1, Expr *item2, Relids qualscope, - Relids nullable_relids); + Relids nullable_relids, + Index security_level); extern void match_foreign_keys_to_quals(PlannerInfo *root); /* diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 05f6ee0d5d2..2b20b36f74f 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -36,11 +36,6 @@ extern Node *negate_clause(Node *node); extern Expr *canonicalize_qual(Expr *qual); /* - * prototypes for prepsecurity.c - */ -extern void expand_security_quals(PlannerInfo *root, List *tlist); - -/* * prototypes for preptlist.c */ extern List *preprocess_targetlist(PlannerInfo *root, List *tlist); diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h index 2dba51fb782..31b9a792859 100644 --- a/src/include/optimizer/restrictinfo.h +++ b/src/include/optimizer/restrictinfo.h @@ -19,20 +19,20 @@ /* Convenience macro for the common case of a valid-everywhere qual */ #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, bool is_pushed_down, bool outerjoin_delayed, bool pseudoconstant, + Index security_level, Relids required_relids, Relids outer_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_securely_promotable(RestrictInfo *restrictinfo, + RelOptInfo *rel); 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, bool pseudoconstant); extern void extract_actual_join_clauses(List *restrictinfo_list, |