pgsql-fix-leaky-join-view.4.patch
text/x-patch
Filename: pgsql-fix-leaky-join-view.4.patch
Type: text/x-patch
Part: 0
Message:
Re: leaky views, yet again
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: unified
| File | + | − |
|---|---|---|
| src/backend/optimizer/plan/initsplan.c | 15 | 1 |
| src/backend/optimizer/util/clauses.c | 115 | 0 |
| src/backend/utils/cache/lsyscache.c | 19 | 0 |
| src/include/optimizer/clauses.h | 1 | 0 |
| src/include/utils/lsyscache.h | 1 | 0 |
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 9cc7b8a..82f7bfb 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -762,6 +762,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool pseudoconstant = false;
bool maybe_equivalence;
bool maybe_outer_join;
+ bool maybe_leakable_clause = false;
Relids nullable_relids;
RestrictInfo *restrictinfo;
@@ -780,6 +781,19 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
elog(ERROR, "JOIN qualification cannot refer to other relations");
/*
+ * If the clause contains a leakable function, it may be used to
+ * bypass row-level security using views. In this case, we don't
+ * push down the clause not to evaluate the leakable clause prior
+ * to the row-level policy functions.
+ */
+ if (!bms_is_empty(relids))
+ {
+ maybe_leakable_clause = contain_leakable_functions(clause);
+ if (maybe_leakable_clause)
+ relids = (!ojscope ? bms_copy(qualscope) : bms_copy(ojscope));
+ }
+
+ /*
* 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.
*
@@ -1030,7 +1044,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
* If none of the above hold, pass it off to
* distribute_restrictinfo_to_rels().
*/
- if (restrictinfo->mergeopfamilies)
+ if (!maybe_leakable_clause && restrictinfo->mergeopfamilies)
{
if (maybe_equivalence)
{
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index c8b2585..74fc64a 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -86,6 +86,7 @@ static bool contain_subplans_walker(Node *node, void *context);
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_leakable_functions_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);
@@ -1129,6 +1130,120 @@ contain_nonstrict_functions_walker(Node *node, void *context)
}
+
+/*****************************************************************************
+ * Check clauses for leakable functions
+ *****************************************************************************/
+
+/*
+ * contain_leakable_functions
+ * Recursively search for leakable functions within a clause.
+ *
+ * Returns true if any function call with side-effect is found.
+ * ie, some type-input/output handler will raise an error when given
+ * argument does not have a valid format.
+ *
+ * When people uses views for row-level security purpose, given qualifiers
+ * come from outside of the view should not be pushed down into the views,
+ * if they have side-effect, because contents of tuples to be filtered out
+ * may be leaked via side-effectable functions within the qualifiers.
+ *
+ * The idea here is that the planner restrain a part of optimization when
+ * the qualifiers contains leakable functions.
+ * This routine checks whether the given clause contains leakable functions,
+ * or not. If we return false, then the clause is clean.
+ */
+bool
+contain_leakable_functions(Node *clause)
+{
+ return contain_leakable_functions_walker(clause, NULL);
+}
+
+static bool
+contain_leakable_functions_walker(Node *node, void *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, FuncExpr))
+ {
+ /*
+ * currently, we have no way to distinguish a safe function and
+ * a leakable one, so all the function call shall be considered
+ * as leakable one.
+ */
+ return true;
+ }
+ else if (IsA(node, OpExpr))
+ {
+ OpExpr *expr = (OpExpr *) node;
+
+ /*
+ * we assume built-in functions to implement operators are not
+ * leakable, so don't need to prevent optimization.
+ */
+ set_opfuncid(expr);
+ if (get_func_lang(expr->opfuncid) != INTERNALlanguageId)
+ return true;
+ /* else fall through to check args */
+ }
+ else if (IsA(node, DistinctExpr))
+ {
+ DistinctExpr *expr = (DistinctExpr *) node;
+
+ set_opfuncid((OpExpr *) expr);
+ if (get_func_lang(expr->opfuncid) != INTERNALlanguageId)
+ return true;
+ /* else fall through to check args */
+ }
+ else if (IsA(node, ScalarArrayOpExpr))
+ {
+ ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
+
+ set_sa_opfuncid(expr);
+ if (get_func_lang(expr->opfuncid) != INTERNALlanguageId)
+ return true;
+ /* else fall through to check args */
+ }
+ else if (IsA(node, CoerceViaIO) ||
+ IsA(node, ArrayCoerceExpr))
+ {
+ /*
+ * we assume type-in/out handlers are leakable, even if built-in
+ * functions.
+ * ie, int4in() raises an error message with given argument,
+ * if it does not have valid format for numeric value.
+ */
+ return true;
+ }
+ else if (IsA(node, NullIfExpr))
+ {
+ NullIfExpr *expr = (NullIfExpr *) node;
+
+ set_opfuncid((OpExpr *) expr); /* rely on struct equivalence */
+ if (get_func_lang(expr->opfuncid) != INTERNALlanguageId)
+ return true;
+ /* else fall through to check args */
+ }
+ else if (IsA(node, RowCompareExpr))
+ {
+ /* RowCompare probably can't have volatile ops, but check anyway */
+ RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+ ListCell *opid;
+
+ foreach(opid, rcexpr->opnos)
+ {
+ Oid funcId = get_opcode(lfirst_oid(opid));
+
+ if (get_func_lang(funcId) != INTERNALlanguageId)
+ return true;
+ }
+ /* else fall through to check args */
+ }
+ return expression_tree_walker(node, contain_leakable_functions_walker,
+ context);
+}
+
/*
* find_nonnullable_rels
* Determine which base rels are forced nonnullable by given clause.
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 19a4a45..196dddf 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1275,6 +1275,25 @@ get_func_namespace(Oid funcid)
}
/*
+ * get_func_lang
+ * Given procedure id, return the function's language
+ */
+Oid
+get_func_lang(Oid funcid)
+{
+ HeapTuple tp;
+ Oid result;
+
+ tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+
+ result = ((Form_pg_proc) GETSTRUCT(tp))->prolang;
+ ReleaseSysCache(tp);
+ return result;
+}
+
+/*
* get_func_rettype
* Given procedure id, return the function's result type.
*/
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index e979596..2f6d86d 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -67,6 +67,7 @@ extern bool contain_subplans(Node *clause);
extern bool contain_mutable_functions(Node *clause);
extern bool contain_volatile_functions(Node *clause);
extern bool contain_nonstrict_functions(Node *clause);
+extern bool contain_leakable_functions(Node *clause);
extern Relids find_nonnullable_rels(Node *clause);
extern List *find_nonnullable_vars(Node *clause);
extern List *find_forced_null_vars(Node *clause);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 066ad76..efffbc7 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -77,6 +77,7 @@ extern RegProcedure get_oprrest(Oid opno);
extern RegProcedure get_oprjoin(Oid opno);
extern char *get_func_name(Oid funcid);
extern Oid get_func_namespace(Oid funcid);
+extern Oid get_func_lang(Oid funcid);
extern Oid get_func_rettype(Oid funcid);
extern int get_func_nargs(Oid funcid);
extern Oid get_func_signature(Oid funcid, Oid **argtypes, int *nargs);