postgres/src/backend/optimizer/prep/prepsecurity.c
2017-01-03 13:48:53 -05:00

487 lines
14 KiB
C

/*-------------------------------------------------------------------------
*
* prepsecurity.c
* Routines for preprocessing security barrier quals.
*
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/optimizer/prep/prepsecurity.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/heapam.h"
#include "access/sysattr.h"
#include "catalog/heap.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/prep.h"
#include "parser/analyze.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/rel.h"
typedef struct
{
int rt_index; /* Index of security barrier RTE */
int sublevels_up; /* Current nesting depth */
Relation rel; /* RTE relation at rt_index */
List *targetlist; /* Targetlist for new subquery RTE */
List *colnames; /* Column names in subquery RTE */
List *vars_processed; /* List of Vars already processed */
} security_barrier_replace_vars_context;
static void expand_security_qual(PlannerInfo *root, List *tlist, int rt_index,
RangeTblEntry *rte, Node *qual, bool targetRelation);
static void security_barrier_replace_vars(Node *node,
security_barrier_replace_vars_context *context);
static bool security_barrier_replace_vars_walker(Node *node,
security_barrier_replace_vars_context *context);
/*
* expand_security_quals -
* expands any security barrier quals on RTEs in the query rtable, turning
* them into security barrier subqueries.
*
* Any given RTE may have multiple security barrier quals in a list, from which
* we create a set of nested subqueries to isolate each security barrier from
* the others, providing protection against malicious user-defined security
* barriers. The first security barrier qual in the list will be used in the
* innermost subquery.
*
* In practice, the only RTEs that will have security barrier quals are those
* that refer to tables with row-level security, or which are the target
* relation of an update to an auto-updatable security barrier view. RTEs
* that read from a security barrier view will have already been expanded by
* the rewriter.
*/
void
expand_security_quals(PlannerInfo *root, List *tlist)
{
Query *parse = root->parse;
int rt_index;
ListCell *cell;
/*
* Process each RTE in the rtable list.
*
* We only ever modify entries in place and append to the rtable, so it is
* safe to use a foreach loop here.
*/
rt_index = 0;
foreach(cell, parse->rtable)
{
bool targetRelation = false;
RangeTblEntry *rte = (RangeTblEntry *) lfirst(cell);
rt_index++;
if (rte->securityQuals == NIL)
continue;
/*
* Ignore any RTEs that aren't used in the query (such RTEs may be
* present for permissions checks).
*/
if (rt_index != parse->resultRelation &&
!rangeTableEntry_used((Node *) parse, rt_index, 0))
continue;
/*
* If this RTE is the target then we need to make a copy of it before
* expanding it. The unexpanded copy will become the new target, and
* the original RTE will be expanded to become the source of rows to
* update/delete.
*/
if (rt_index == parse->resultRelation)
{
RangeTblEntry *newrte = copyObject(rte);
/*
* We need to let expand_security_qual know if this is the target
* relation, as it has additional work to do in that case.
*
* Capture that information here as we're about to replace
* parse->resultRelation.
*/
targetRelation = true;
parse->rtable = lappend(parse->rtable, newrte);
parse->resultRelation = list_length(parse->rtable);
/*
* Wipe out any copied security barrier quals on the new target to
* prevent infinite recursion.
*/
newrte->securityQuals = NIL;
/*
* There's no need to do permissions checks twice, so wipe out the
* permissions info for the original RTE (we prefer to keep the
* bits set on the result RTE).
*/
rte->requiredPerms = 0;
rte->checkAsUser = InvalidOid;
rte->selectedCols = NULL;
rte->insertedCols = NULL;
rte->updatedCols = NULL;
/*
* For the most part, Vars referencing the original relation
* should remain as they are, meaning that they pull OLD values
* from the expanded RTE. But in the RETURNING list and in any
* WITH CHECK OPTION quals, we want such Vars to represent NEW
* values, so change them to reference the new RTE.
*/
ChangeVarNodes((Node *) parse->returningList, rt_index,
parse->resultRelation, 0);
ChangeVarNodes((Node *) parse->withCheckOptions, rt_index,
parse->resultRelation, 0);
}
/*
* Process each security barrier qual in turn, starting with the
* innermost one (the first in the list) and working outwards.
*
* We remove each qual from the list before processing it, so that its
* variables aren't modified by expand_security_qual. Also we don't
* necessarily want the attributes referred to by the qual to be
* exposed by the newly built subquery.
*/
while (rte->securityQuals != NIL)
{
Node *qual = (Node *) linitial(rte->securityQuals);
rte->securityQuals = list_delete_first(rte->securityQuals);
ChangeVarNodes(qual, rt_index, 1, 0);
expand_security_qual(root, tlist, rt_index, rte, qual,
targetRelation);
}
}
}
/*
* expand_security_qual -
* expand the specified security barrier qual on a query RTE, turning the
* RTE into a security barrier subquery.
*/
static void
expand_security_qual(PlannerInfo *root, List *tlist, int rt_index,
RangeTblEntry *rte, Node *qual, bool targetRelation)
{
Query *parse = root->parse;
Oid relid = rte->relid;
Query *subquery;
RangeTblEntry *subrte;
RangeTblRef *subrtr;
PlanRowMark *rc;
security_barrier_replace_vars_context context;
ListCell *cell;
/*
* There should only be 2 possible cases:
*
* 1. A relation RTE, which we turn into a subquery RTE containing all
* referenced columns.
*
* 2. A subquery RTE (either from a prior call to this function or from an
* expanded view). In this case we build a new subquery on top of it to
* isolate this security barrier qual from any other quals.
*/
switch (rte->rtekind)
{
case RTE_RELATION:
/*
* Turn the relation RTE into a security barrier subquery RTE,
* moving all permissions checks down into the subquery.
*/
subquery = makeNode(Query);
subquery->commandType = CMD_SELECT;
subquery->querySource = QSRC_INSTEAD_RULE;
subrte = copyObject(rte);
subrte->inFromCl = true;
subrte->securityQuals = NIL;
subquery->rtable = list_make1(subrte);
subrtr = makeNode(RangeTblRef);
subrtr->rtindex = 1;
subquery->jointree = makeFromExpr(list_make1(subrtr), qual);
subquery->hasSubLinks = checkExprHasSubLink(qual);
rte->rtekind = RTE_SUBQUERY;
rte->relid = InvalidOid;
rte->subquery = subquery;
rte->security_barrier = true;
rte->inh = false; /* must not be set for a subquery */
/* the permissions checks have now been moved down */
rte->requiredPerms = 0;
rte->checkAsUser = InvalidOid;
rte->selectedCols = NULL;
rte->insertedCols = NULL;
rte->updatedCols = NULL;
/*
* Now deal with any PlanRowMark on this RTE by requesting a lock
* of the same strength on the RTE copied down to the subquery.
*
* Note that we can only push down user-defined quals if they are
* only using leakproof (and therefore trusted) functions and
* operators. As a result, we may end up locking more rows than
* strictly necessary (and, in the worst case, we could end up
* locking all rows which pass the securityQuals). This is
* currently documented behavior, but it'd be nice to come up with
* a better solution some day.
*/
rc = get_plan_rowmark(root->rowMarks, rt_index);
if (rc != NULL)
{
if (rc->strength != LCS_NONE)
applyLockingClause(subquery, 1, rc->strength,
rc->waitPolicy, false);
root->rowMarks = list_delete_ptr(root->rowMarks, rc);
}
/*
* When we are replacing the target relation with a subquery, we
* need to make sure to add a locking clause explicitly to the
* generated subquery since there won't be any row marks against
* the target relation itself.
*/
if (targetRelation)
applyLockingClause(subquery, 1, LCS_FORUPDATE,
LockWaitBlock, false);
/*
* Replace any variables in the outer query that refer to the
* original relation RTE with references to columns that we will
* expose in the new subquery, building the subquery's targetlist
* as we go. Also replace any references in the translated_vars
* lists of any appendrels.
*/
context.rt_index = rt_index;
context.sublevels_up = 0;
context.rel = heap_open(relid, NoLock);
context.targetlist = NIL;
context.colnames = NIL;
context.vars_processed = NIL;
security_barrier_replace_vars((Node *) parse, &context);
security_barrier_replace_vars((Node *) tlist, &context);
security_barrier_replace_vars((Node *) root->append_rel_list,
&context);
heap_close(context.rel, NoLock);
/* Now we know what columns the subquery needs to expose */
rte->subquery->targetList = context.targetlist;
rte->eref = makeAlias(rte->eref->aliasname, context.colnames);
break;
case RTE_SUBQUERY:
/*
* Build a new subquery that includes all the same columns as the
* original subquery.
*/
subquery = makeNode(Query);
subquery->commandType = CMD_SELECT;
subquery->querySource = QSRC_INSTEAD_RULE;
subquery->targetList = NIL;
foreach(cell, rte->subquery->targetList)
{
TargetEntry *tle;
Var *var;
tle = (TargetEntry *) lfirst(cell);
var = makeVarFromTargetEntry(1, tle);
tle = makeTargetEntry((Expr *) var,
list_length(subquery->targetList) + 1,
pstrdup(tle->resname),
tle->resjunk);
subquery->targetList = lappend(subquery->targetList, tle);
}
subrte = makeNode(RangeTblEntry);
subrte->rtekind = RTE_SUBQUERY;
subrte->subquery = rte->subquery;
subrte->security_barrier = rte->security_barrier;
subrte->eref = copyObject(rte->eref);
subrte->inFromCl = true;
subquery->rtable = list_make1(subrte);
subrtr = makeNode(RangeTblRef);
subrtr->rtindex = 1;
subquery->jointree = makeFromExpr(list_make1(subrtr), qual);
subquery->hasSubLinks = checkExprHasSubLink(qual);
rte->subquery = subquery;
rte->security_barrier = true;
break;
default:
elog(ERROR, "invalid range table entry for security barrier qual");
}
}
/*
* security_barrier_replace_vars -
* Apply security barrier variable replacement to an expression tree.
*
* This also builds/updates a targetlist with entries for each replacement
* variable that needs to be exposed by the security barrier subquery RTE.
*
* NOTE: although this has the form of a walker, we cheat and modify the
* nodes in-place. The given expression tree should have been copied
* earlier to ensure that no unwanted side-effects occur!
*/
static void
security_barrier_replace_vars(Node *node,
security_barrier_replace_vars_context *context)
{
/*
* Must be prepared to start with a Query or a bare expression tree; if
* 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))
query_tree_walker((Query *) node,
security_barrier_replace_vars_walker,
(void *) context, 0);
else
security_barrier_replace_vars_walker(node, context);
}
static bool
security_barrier_replace_vars_walker(Node *node,
security_barrier_replace_vars_context *context)
{
if (node == NULL)
return false;
if (IsA(node, Var))
{
Var *var = (Var *) node;
/*
* Note that the same Var may be present in different lists, so we
* need to take care not to process it multiple times.
*/
if (var->varno == context->rt_index &&
var->varlevelsup == context->sublevels_up &&
!list_member_ptr(context->vars_processed, var))
{
/*
* Found a matching variable. Make sure that it is in the subquery
* targetlist and map its attno accordingly.
*/
AttrNumber attno;
ListCell *l;
TargetEntry *tle;
char *attname;
Var *newvar;
/* Search for the base attribute in the subquery targetlist */
attno = InvalidAttrNumber;
foreach(l, context->targetlist)
{
tle = (TargetEntry *) lfirst(l);
attno++;
Assert(IsA(tle->expr, Var));
if (((Var *) tle->expr)->varattno == var->varattno &&
((Var *) tle->expr)->varcollid == var->varcollid)
{
/* Map the variable onto this subquery targetlist entry */
var->varattno = var->varoattno = attno;
/* Mark this var as having been processed */
context->vars_processed = lappend(context->vars_processed, var);
return false;
}
}
/* Not in the subquery targetlist, so add it. Get its name. */
if (var->varattno < 0)
{
Form_pg_attribute att_tup;
att_tup = SystemAttributeDefinition(var->varattno,
context->rel->rd_rel->relhasoids);
attname = NameStr(att_tup->attname);
}
else if (var->varattno == InvalidAttrNumber)
{
attname = "wholerow";
}
else if (var->varattno <= context->rel->rd_att->natts)
{
Form_pg_attribute att_tup;
att_tup = context->rel->rd_att->attrs[var->varattno - 1];
attname = NameStr(att_tup->attname);
}
else
{
elog(ERROR, "invalid attribute number %d in security_barrier_replace_vars", var->varattno);
}
/* New variable for subquery targetlist */
newvar = copyObject(var);
newvar->varno = newvar->varnoold = 1;
newvar->varlevelsup = 0;
attno = list_length(context->targetlist) + 1;
tle = makeTargetEntry((Expr *) newvar,
attno,
pstrdup(attname),
false);
context->targetlist = lappend(context->targetlist, tle);
context->colnames = lappend(context->colnames,
makeString(pstrdup(attname)));
/* Update the outer query's variable */
var->varattno = var->varoattno = attno;
/* Remember this Var so that we don't process it again */
context->vars_processed = lappend(context->vars_processed, var);
}
return false;
}
if (IsA(node, Query))
{
/* Recurse into subselects */
bool result;
context->sublevels_up++;
result = query_tree_walker((Query *) node,
security_barrier_replace_vars_walker,
(void *) context, 0);
context->sublevels_up--;
return result;
}
return expression_tree_walker(node, security_barrier_replace_vars_walker,
(void *) context);
}