diff options
Diffstat (limited to 'src/backend/optimizer/prep')
| -rw-r--r-- | src/backend/optimizer/prep/Makefile | 2 | ||||
| -rw-r--r-- | src/backend/optimizer/prep/prepsecurity.c | 466 | ||||
| -rw-r--r-- | src/backend/optimizer/prep/prepunion.c | 60 |
3 files changed, 513 insertions, 15 deletions
diff --git a/src/backend/optimizer/prep/Makefile b/src/backend/optimizer/prep/Makefile index 86301bfbd3..5195d9b0ba 100644 --- a/src/backend/optimizer/prep/Makefile +++ b/src/backend/optimizer/prep/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/optimizer/prep top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = prepjointree.o prepqual.o preptlist.o prepunion.o +OBJS = prepjointree.o prepqual.o prepsecurity.o preptlist.o prepunion.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/prep/prepsecurity.c b/src/backend/optimizer/prep/prepsecurity.c new file mode 100644 index 0000000000..7daaa3349e --- /dev/null +++ b/src/backend/optimizer/prep/prepsecurity.c @@ -0,0 +1,466 @@ +/*------------------------------------------------------------------------- + * + * prepsecurity.c + * Routines for preprocessing security barrier quals. + * + * Portions Copyright (c) 1996-2013, 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); + +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. + */ +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) + { + 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); + 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->modifiedCols = 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); + } + } +} + + +/* + * 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) +{ + 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->modifiedCols = 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't push the user-defined quals down since they + * may included untrusted functions and that means that we will + * end up locking all rows which pass the securityQuals, even if + * those rows don't pass the user-defined quals. 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) + { + switch (rc->markType) + { + case ROW_MARK_EXCLUSIVE: + applyLockingClause(subquery, 1, LCS_FORUPDATE, + rc->noWait, false); + break; + case ROW_MARK_NOKEYEXCLUSIVE: + applyLockingClause(subquery, 1, LCS_FORNOKEYUPDATE, + rc->noWait, false); + break; + case ROW_MARK_SHARE: + applyLockingClause(subquery, 1, LCS_FORSHARE, + rc->noWait, false); + break; + case ROW_MARK_KEYSHARE: + applyLockingClause(subquery, 1, LCS_FORKEYSHARE, + rc->noWait, false); + break; + case ROW_MARK_REFERENCE: + case ROW_MARK_COPY: + /* No locking needed */ + break; + } + root->rowMarks = list_delete(root->rowMarks, rc); + } + + /* + * 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. + */ + 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); + + 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 = attno; + 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 = 1; + + 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 = 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); +} diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 52dcc720de..cdf541d34d 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -55,6 +55,7 @@ typedef struct { PlannerInfo *root; AppendRelInfo *appinfo; + int sublevels_up; } adjust_appendrel_attrs_context; static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root, @@ -1580,8 +1581,9 @@ translate_col_privs(const Bitmapset *parent_privs, * child rel instead. We also update rtindexes appearing outside Vars, * such as resultRelation and jointree relids. * - * Note: this is only applied after conversion of sublinks to subplans, - * so we don't need to cope with recursion into sub-queries. + * Note: this is applied after conversion of sublinks to subplans in the + * query jointree, but there may still be sublinks in the security barrier + * quals of RTEs, so we do need to cope with recursion into sub-queries. * * Note: this is not hugely different from what pullup_replace_vars() does; * maybe we should try to fold the two routines together. @@ -1594,9 +1596,12 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo) context.root = root; context.appinfo = appinfo; + context.sublevels_up = 0; /* - * Must be prepared to start with a Query or a bare expression tree. + * 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)) { @@ -1635,7 +1640,7 @@ adjust_appendrel_attrs_mutator(Node *node, { Var *var = (Var *) copyObject(node); - if (var->varlevelsup == 0 && + if (var->varlevelsup == context->sublevels_up && var->varno == appinfo->parent_relid) { var->varno = appinfo->child_relid; @@ -1652,6 +1657,7 @@ adjust_appendrel_attrs_mutator(Node *node, if (newnode == NULL) elog(ERROR, "attribute %d of relation \"%s\" does not exist", var->varattno, get_rel_name(appinfo->parent_reloid)); + ((Var *) newnode)->varlevelsup += context->sublevels_up; return newnode; } else if (var->varattno == 0) @@ -1694,10 +1700,16 @@ adjust_appendrel_attrs_mutator(Node *node, RowExpr *rowexpr; List *fields; RangeTblEntry *rte; + ListCell *lc; rte = rt_fetch(appinfo->parent_relid, context->root->parse->rtable); fields = (List *) copyObject(appinfo->translated_vars); + foreach(lc, fields) + { + Var *field = (Var *) lfirst(lc); + field->varlevelsup += context->sublevels_up; + } rowexpr = makeNode(RowExpr); rowexpr->args = fields; rowexpr->row_typeid = var->vartype; @@ -1716,7 +1728,8 @@ adjust_appendrel_attrs_mutator(Node *node, { CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); - if (cexpr->cvarno == appinfo->parent_relid) + if (context->sublevels_up == 0 && + cexpr->cvarno == appinfo->parent_relid) cexpr->cvarno = appinfo->child_relid; return (Node *) cexpr; } @@ -1724,7 +1737,8 @@ adjust_appendrel_attrs_mutator(Node *node, { RangeTblRef *rtr = (RangeTblRef *) copyObject(node); - if (rtr->rtindex == appinfo->parent_relid) + if (context->sublevels_up == 0 && + rtr->rtindex == appinfo->parent_relid) rtr->rtindex = appinfo->child_relid; return (Node *) rtr; } @@ -1737,7 +1751,8 @@ adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_mutator, (void *) context); /* now fix JoinExpr's rtindex (probably never happens) */ - if (j->rtindex == appinfo->parent_relid) + if (context->sublevels_up == 0 && + j->rtindex == appinfo->parent_relid) j->rtindex = appinfo->child_relid; return (Node *) j; } @@ -1750,7 +1765,7 @@ adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_mutator, (void *) context); /* now fix PlaceHolderVar's relid sets */ - if (phv->phlevelsup == 0) + if (phv->phlevelsup == context->sublevels_up) phv->phrels = adjust_relid_set(phv->phrels, appinfo->parent_relid, appinfo->child_relid); @@ -1822,12 +1837,29 @@ adjust_appendrel_attrs_mutator(Node *node, return (Node *) newinfo; } - /* - * NOTE: we do not need to recurse into sublinks, because they should - * already have been converted to subplans before we see them. - */ - Assert(!IsA(node, SubLink)); - Assert(!IsA(node, Query)); + if (IsA(node, Query)) + { + /* + * Recurse into sublink subqueries. This should only be possible in + * security barrier quals of top-level RTEs. All other sublinks should + * have already been converted to subplans during expression + * preprocessing, but this doesn't happen for security barrier quals, + * since they are destined to become quals of a subquery RTE, which + * will be recursively planned, and so should not be preprocessed at + * this stage. + * + * We don't explicitly Assert() for securityQuals here simply because + * it's not trivial to do so. + */ + Query *newnode; + + context->sublevels_up++; + newnode = query_tree_mutator((Query *) node, + adjust_appendrel_attrs_mutator, + (void *) context, 0); + context->sublevels_up--; + return (Node *) newnode; + } return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, (void *) context); |
