summaryrefslogtreecommitdiff
path: root/src/backend/optimizer/prep/prepsecurity.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer/prep/prepsecurity.c')
-rw-r--r--src/backend/optimizer/prep/prepsecurity.c466
1 files changed, 466 insertions, 0 deletions
diff --git a/src/backend/optimizer/prep/prepsecurity.c b/src/backend/optimizer/prep/prepsecurity.c
new file mode 100644
index 00000000000..7daaa3349ed
--- /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);
+}