mirror of
https://github.com/postgres/postgres.git
synced 2025-12-06 00:02:13 +03:00
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
271 lines
7.0 KiB
C
271 lines
7.0 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* tidpath.c
|
|
* Routines to determine which TID conditions are usable for scanning
|
|
* a given relation, and create TidPaths accordingly.
|
|
*
|
|
* What we are looking for here is WHERE conditions of the form
|
|
* "CTID = pseudoconstant", which can be implemented by just fetching
|
|
* the tuple directly via heap_fetch(). We can also handle OR'd conditions
|
|
* such as (CTID = const1) OR (CTID = const2), as well as ScalarArrayOpExpr
|
|
* conditions of the form CTID = ANY(pseudoconstant_array). In particular
|
|
* this allows
|
|
* WHERE ctid IN (tid1, tid2, ...)
|
|
*
|
|
* We also support "WHERE CURRENT OF cursor" conditions (CurrentOfExpr),
|
|
* which amount to "CTID = run-time-determined-TID". These could in
|
|
* theory be translated to a simple comparison of CTID to the result of
|
|
* a function, but in practice it works better to keep the special node
|
|
* representation all the way through to execution.
|
|
*
|
|
* There is currently no special support for joins involving CTID; in
|
|
* particular nothing corresponding to best_inner_indexscan(). Since it's
|
|
* not very useful to store TIDs of one table in another table, there
|
|
* doesn't seem to be enough use-case to justify adding a lot of code
|
|
* for that.
|
|
*
|
|
*
|
|
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/optimizer/path/tidpath.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "access/sysattr.h"
|
|
#include "catalog/pg_operator.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "optimizer/clauses.h"
|
|
#include "optimizer/pathnode.h"
|
|
#include "optimizer/paths.h"
|
|
#include "optimizer/restrictinfo.h"
|
|
|
|
|
|
static bool IsTidEqualClause(OpExpr *node, int varno);
|
|
static bool IsTidEqualAnyClause(ScalarArrayOpExpr *node, int varno);
|
|
static List *TidQualFromExpr(Node *expr, int varno);
|
|
static List *TidQualFromBaseRestrictinfo(RelOptInfo *rel);
|
|
|
|
|
|
/*
|
|
* Check to see if an opclause is of the form
|
|
* CTID = pseudoconstant
|
|
* or
|
|
* pseudoconstant = CTID
|
|
*
|
|
* We check that the CTID Var belongs to relation "varno". That is probably
|
|
* redundant considering this is only applied to restriction clauses, but
|
|
* let's be safe.
|
|
*/
|
|
static bool
|
|
IsTidEqualClause(OpExpr *node, int varno)
|
|
{
|
|
Node *arg1,
|
|
*arg2,
|
|
*other;
|
|
Var *var;
|
|
|
|
/* Operator must be tideq */
|
|
if (node->opno != TIDEqualOperator)
|
|
return false;
|
|
if (list_length(node->args) != 2)
|
|
return false;
|
|
arg1 = linitial(node->args);
|
|
arg2 = lsecond(node->args);
|
|
|
|
/* Look for CTID as either argument */
|
|
other = NULL;
|
|
if (arg1 && IsA(arg1, Var))
|
|
{
|
|
var = (Var *) arg1;
|
|
if (var->varattno == SelfItemPointerAttributeNumber &&
|
|
var->vartype == TIDOID &&
|
|
var->varno == varno &&
|
|
var->varlevelsup == 0)
|
|
other = arg2;
|
|
}
|
|
if (!other && arg2 && IsA(arg2, Var))
|
|
{
|
|
var = (Var *) arg2;
|
|
if (var->varattno == SelfItemPointerAttributeNumber &&
|
|
var->vartype == TIDOID &&
|
|
var->varno == varno &&
|
|
var->varlevelsup == 0)
|
|
other = arg1;
|
|
}
|
|
if (!other)
|
|
return false;
|
|
if (exprType(other) != TIDOID)
|
|
return false; /* probably can't happen */
|
|
|
|
/* The other argument must be a pseudoconstant */
|
|
if (!is_pseudo_constant_clause(other))
|
|
return false;
|
|
|
|
return true; /* success */
|
|
}
|
|
|
|
/*
|
|
* Check to see if a clause is of the form
|
|
* CTID = ANY (pseudoconstant_array)
|
|
*/
|
|
static bool
|
|
IsTidEqualAnyClause(ScalarArrayOpExpr *node, int varno)
|
|
{
|
|
Node *arg1,
|
|
*arg2;
|
|
|
|
/* Operator must be tideq */
|
|
if (node->opno != TIDEqualOperator)
|
|
return false;
|
|
if (!node->useOr)
|
|
return false;
|
|
Assert(list_length(node->args) == 2);
|
|
arg1 = linitial(node->args);
|
|
arg2 = lsecond(node->args);
|
|
|
|
/* CTID must be first argument */
|
|
if (arg1 && IsA(arg1, Var))
|
|
{
|
|
Var *var = (Var *) arg1;
|
|
|
|
if (var->varattno == SelfItemPointerAttributeNumber &&
|
|
var->vartype == TIDOID &&
|
|
var->varno == varno &&
|
|
var->varlevelsup == 0)
|
|
{
|
|
/* The other argument must be a pseudoconstant */
|
|
if (is_pseudo_constant_clause(arg2))
|
|
return true; /* success */
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Extract a set of CTID conditions from the given qual expression
|
|
*
|
|
* Returns a List of CTID qual expressions (with implicit OR semantics
|
|
* across the list), or NIL if there are no usable conditions.
|
|
*
|
|
* If the expression is an AND clause, we can use a CTID condition
|
|
* from any sub-clause. If it is an OR clause, we must be able to
|
|
* extract a CTID condition from every sub-clause, or we can't use it.
|
|
*
|
|
* In theory, in the AND case we could get CTID conditions from different
|
|
* sub-clauses, in which case we could try to pick the most efficient one.
|
|
* In practice, such usage seems very unlikely, so we don't bother; we
|
|
* just exit as soon as we find the first candidate.
|
|
*/
|
|
static List *
|
|
TidQualFromExpr(Node *expr, int varno)
|
|
{
|
|
List *rlst = NIL;
|
|
ListCell *l;
|
|
|
|
if (is_opclause(expr))
|
|
{
|
|
/* base case: check for tideq opclause */
|
|
if (IsTidEqualClause((OpExpr *) expr, varno))
|
|
rlst = list_make1(expr);
|
|
}
|
|
else if (expr && IsA(expr, ScalarArrayOpExpr))
|
|
{
|
|
/* another base case: check for tid = ANY clause */
|
|
if (IsTidEqualAnyClause((ScalarArrayOpExpr *) expr, varno))
|
|
rlst = list_make1(expr);
|
|
}
|
|
else if (expr && IsA(expr, CurrentOfExpr))
|
|
{
|
|
/* another base case: check for CURRENT OF on this rel */
|
|
if (((CurrentOfExpr *) expr)->cvarno == varno)
|
|
rlst = list_make1(expr);
|
|
}
|
|
else if (and_clause(expr))
|
|
{
|
|
foreach(l, ((BoolExpr *) expr)->args)
|
|
{
|
|
rlst = TidQualFromExpr((Node *) lfirst(l), varno);
|
|
if (rlst)
|
|
break;
|
|
}
|
|
}
|
|
else if (or_clause(expr))
|
|
{
|
|
foreach(l, ((BoolExpr *) expr)->args)
|
|
{
|
|
List *frtn = TidQualFromExpr((Node *) lfirst(l), varno);
|
|
|
|
if (frtn)
|
|
rlst = list_concat(rlst, frtn);
|
|
else
|
|
{
|
|
if (rlst)
|
|
list_free(rlst);
|
|
rlst = NIL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return rlst;
|
|
}
|
|
|
|
/*
|
|
* Extract a set of CTID conditions from the rel's baserestrictinfo list
|
|
*/
|
|
static List *
|
|
TidQualFromBaseRestrictinfo(RelOptInfo *rel)
|
|
{
|
|
List *rlst = NIL;
|
|
ListCell *l;
|
|
|
|
foreach(l, rel->baserestrictinfo)
|
|
{
|
|
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
|
|
|
|
/*
|
|
* If clause must wait till after some lower-security-level
|
|
* restriction clause, reject it.
|
|
*/
|
|
if (!restriction_is_securely_promotable(rinfo, rel))
|
|
continue;
|
|
|
|
rlst = TidQualFromExpr((Node *) rinfo->clause, rel->relid);
|
|
if (rlst)
|
|
break;
|
|
}
|
|
return rlst;
|
|
}
|
|
|
|
/*
|
|
* create_tidscan_paths
|
|
* Create paths corresponding to direct TID scans of the given rel.
|
|
*
|
|
* Candidate paths are added to the rel's pathlist (using add_path).
|
|
*/
|
|
void
|
|
create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel)
|
|
{
|
|
Relids required_outer;
|
|
List *tidquals;
|
|
|
|
/*
|
|
* We don't support pushing join clauses into the quals of a tidscan, but
|
|
* it could still have required parameterization due to LATERAL refs in
|
|
* its tlist.
|
|
*/
|
|
required_outer = rel->lateral_relids;
|
|
|
|
tidquals = TidQualFromBaseRestrictinfo(rel);
|
|
|
|
if (tidquals)
|
|
add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
|
|
required_outer));
|
|
}
|