pgsql-fix-leaky-join-view.1.patch
text/x-patch
Filename: pgsql-fix-leaky-join-view.1.patch
Type: text/x-patch
Part: 0
Message:
[PATCH] Fix leaky VIEWs for RLS
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: context
| File | + | − |
|---|---|---|
| src/backend/nodes/copyfuncs.c | 1 | 0 |
| src/backend/nodes/equalfuncs.c | 1 | 0 |
| src/backend/nodes/makefuncs.c | 1 | 0 |
| src/backend/nodes/outfuncs.c | 1 | 0 |
| src/backend/nodes/readfuncs.c | 1 | 0 |
| src/backend/optimizer/plan/initsplan.c | 62 | 0 |
| src/backend/optimizer/prep/prepjointree.c | 19 | 0 |
| src/backend/optimizer/util/clauses.c | 28 | 0 |
| src/include/nodes/primnodes.h | 1 | 0 |
| src/include/optimizer/clauses.h | 1 | 0 |
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1651,1656 **** _copyFromExpr(FromExpr *from)
--- 1651,1657 ----
COPY_NODE_FIELD(fromlist);
COPY_NODE_FIELD(quals);
+ COPY_SCALAR_FIELD(security_view);
return newnode;
}
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 730,735 **** _equalFromExpr(FromExpr *a, FromExpr *b)
--- 730,736 ----
{
COMPARE_NODE_FIELD(fromlist);
COMPARE_NODE_FIELD(quals);
+ COMPARE_SCALAR_FIELD(security_view);
return true;
}
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 148,153 **** makeFromExpr(List *fromlist, Node *quals)
--- 148,154 ----
f->fromlist = fromlist;
f->quals = quals;
+ f->security_view = false;
return f;
}
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 1329,1334 **** _outFromExpr(StringInfo str, FromExpr *node)
--- 1329,1335 ----
WRITE_NODE_FIELD(fromlist);
WRITE_NODE_FIELD(quals);
+ WRITE_BOOL_FIELD(security_view);
}
/*****************************************************************************
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 1109,1114 **** _readFromExpr(void)
--- 1109,1115 ----
READ_NODE_FIELD(fromlist);
READ_NODE_FIELD(quals);
+ READ_BOOL_FIELD(security_view);
READ_DONE();
}
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
***************
*** 718,723 **** make_outerjoininfo(PlannerInfo *root,
--- 718,779 ----
*****************************************************************************/
/*
+ * expand_relids_on_view
+ *
+ * It expands the given relids when it tries to reference a part of relations
+ * within security views. If FromExpr->security_view is true, it means current
+ * user didn't have privileges to access underlaying tables without views.
+ * If user given function takes arguments which references only one-side of
+ * the join loop, optimization may distribute the function node into inside
+ * of the join. It has a possibility to leak contents of invisible tuples to
+ * be filtered using views.
+ */
+ static Relids
+ expand_relids_on_view(Node *jtnode, Relids relids)
+ {
+ if (IsA(jtnode, FromExpr))
+ {
+ FromExpr *f = (FromExpr *) jtnode;
+ ListCell *l;
+
+ foreach (l, f->fromlist)
+ {
+ if (f->security_view)
+ {
+ /*
+ * All the node under the FromExpr with security_view = true
+ * are originally come from a view with underlaying tables
+ * which are not accessable from a person executing the query.
+ * So, if the given clause depends on a part of the relations,
+ * we expand its dependency into whole of the security view.
+ */
+ Relids temp = get_relids_in_jointree((Node *)lfirst(l), false);
+
+ if (bms_overlap(relids, temp))
+ relids = bms_add_members(relids, temp);
+ }
+ else
+ {
+ relids = expand_relids_on_view((Node *)lfirst(l), relids);
+ }
+ }
+ }
+ else if (IsA(jtnode, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) jtnode;
+
+ relids = expand_relids_on_view(j->larg, relids);
+ relids = expand_relids_on_view(j->rarg, relids);
+ }
+ else if (!IsA(jtnode, RangeTblRef))
+ {
+ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(jtnode));
+ }
+
+ return relids;
+ }
+
+ /*
* distribute_qual_to_rels
* Add clause information to either the baserestrictinfo or joininfo list
* (depending on whether the clause is a join) of each base relation
***************
*** 780,785 **** distribute_qual_to_rels(PlannerInfo *root, Node *clause,
--- 836,847 ----
elog(ERROR, "JOIN qualification cannot refer to other relations");
/*
+ * Ensure user given quals not to come into join loop
+ */
+ if (contain_functions_expr(clause))
+ relids = expand_relids_on_view((Node *)root->parse->jointree, relids);
+
+ /*
* If the clause is variable-free, our normal heuristic for pushing it
* down to just the mentioned rels doesn't work, because there are none.
*
*** a/src/backend/optimizer/prep/prepjointree.c
--- b/src/backend/optimizer/prep/prepjointree.c
***************
*** 34,39 ****
--- 34,40 ----
#include "parser/parse_relation.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
+ #include "utils/acl.h"
typedef struct pullup_replace_vars_context
***************
*** 601,606 **** pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
--- 602,608 ----
PlannerInfo *subroot;
int rtoffset;
pullup_replace_vars_context rvcontext;
+ List *tempList;
ListCell *lc;
/*
***************
*** 829,834 **** pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
--- 831,853 ----
Assert(subroot->placeholder_list == NIL);
/*
+ * In the case when the subquery came from a view, we have to disable
+ * a part of optimization for the security reason.
+ * If user provides a function with side-effects in WHERE clause and
+ * it depends on a part of relations within a view, contents of the
+ * invisible tuples might leak somewhere via user given functions.
+ * When FromExpr->security_view is true, it prevents these functions
+ * are distributed into inside of the subquery.
+ * We assume it is harmless, if current user has enough privileges
+ * on the underlaying tables.
+ */
+ tempList = copyObject(subquery->rtable);
+ foreach (lc, tempList)
+ ((RangeTblEntry *) lfirst(lc))->checkAsUser = InvalidOid;
+ if (check_relation_privileges(tempList, false) != ACLCHECK_OK)
+ subquery->jointree->security_view = true;
+
+ /*
* Miscellaneous housekeeping.
*
* Although replace_rte_variables() faithfully updated parse->hasSubLinks
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
***************
*** 86,91 **** static bool contain_subplans_walker(Node *node, void *context);
--- 86,92 ----
static bool contain_mutable_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
+ static bool contain_functions_expr_walker(Node *node, void *context);
static Relids find_nonnullable_rels_walker(Node *node, bool top_level);
static List *find_nonnullable_vars_walker(Node *node, bool top_level);
static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
***************
*** 1116,1121 **** contain_nonstrict_functions_walker(Node *node, void *context)
--- 1117,1149 ----
context);
}
+ /*****************************************************************************
+ * Check clauses for function expr
+ *****************************************************************************/
+
+ /*
+ * contain_functions_expr
+ * Recursively search for functions expr within a clause.
+ *
+ * Returns true if FuncExpr is found.
+ */
+ bool
+ contain_functions_expr(Node *clause)
+ {
+ return contain_functions_expr_walker(clause, NULL);
+ }
+
+ static bool
+ contain_functions_expr_walker(Node *node, void *context)
+ {
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, FuncExpr))
+ return true;
+
+ return expression_tree_walker(node, contain_functions_expr_walker, context);
+ }
/*
* find_nonnullable_rels
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 1207,1212 **** typedef struct FromExpr
--- 1207,1213 ----
NodeTag type;
List *fromlist; /* List of join subtrees */
Node *quals; /* qualifiers on join, if any */
+ bool security_view; /* true, if leading FromExpr of security view */
} FromExpr;
#endif /* PRIMNODES_H */
*** a/src/include/optimizer/clauses.h
--- b/src/include/optimizer/clauses.h
***************
*** 67,72 **** extern bool contain_subplans(Node *clause);
--- 67,73 ----
extern bool contain_mutable_functions(Node *clause);
extern bool contain_volatile_functions(Node *clause);
extern bool contain_nonstrict_functions(Node *clause);
+ extern bool contain_functions_expr(Node *clause);
extern Relids find_nonnullable_rels(Node *clause);
extern List *find_nonnullable_vars(Node *clause);
extern List *find_forced_null_vars(Node *clause);