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);