diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/catalog/pg_aggregate.c | 1 | ||||
-rw-r--r-- | src/backend/catalog/pg_proc.c | 2 | ||||
-rw-r--r-- | src/backend/commands/functioncmds.c | 40 | ||||
-rw-r--r-- | src/backend/commands/proclang.c | 3 | ||||
-rw-r--r-- | src/backend/commands/typecmds.c | 1 | ||||
-rw-r--r-- | src/backend/optimizer/path/allpaths.c | 11 | ||||
-rw-r--r-- | src/backend/optimizer/util/clauses.c | 140 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 7 | ||||
-rw-r--r-- | src/backend/utils/cache/lsyscache.c | 19 |
9 files changed, 212 insertions, 12 deletions
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c index 1c4d6460144..9ff70a52e6c 100644 --- a/src/backend/catalog/pg_aggregate.c +++ b/src/backend/catalog/pg_aggregate.c @@ -241,6 +241,7 @@ AggregateCreate(const char *aggName, false, /* isWindowFunc */ false, /* security invoker (currently not * definable for agg) */ + false, /* isLeakProof */ false, /* isStrict (not needed for agg) */ PROVOLATILE_IMMUTABLE, /* volatility (not * needed for agg) */ diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 8c0d5f9e6c4..91ead4cb9d2 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -76,6 +76,7 @@ ProcedureCreate(const char *procedureName, bool isAgg, bool isWindowFunc, bool security_definer, + bool isLeakProof, bool isStrict, char volatility, oidvector *parameterTypes, @@ -334,6 +335,7 @@ ProcedureCreate(const char *procedureName, values[Anum_pg_proc_proisagg - 1] = BoolGetDatum(isAgg); values[Anum_pg_proc_proiswindow - 1] = BoolGetDatum(isWindowFunc); values[Anum_pg_proc_prosecdef - 1] = BoolGetDatum(security_definer); + values[Anum_pg_proc_proleakproof - 1] = BoolGetDatum(isLeakProof); values[Anum_pg_proc_proisstrict - 1] = BoolGetDatum(isStrict); values[Anum_pg_proc_proretset - 1] = BoolGetDatum(returnsSet); values[Anum_pg_proc_provolatile - 1] = CharGetDatum(volatility); diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index c692a658340..ce866a20a99 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -446,6 +446,7 @@ compute_common_attribute(DefElem *defel, DefElem **volatility_item, DefElem **strict_item, DefElem **security_item, + DefElem **leakproof_item, List **set_items, DefElem **cost_item, DefElem **rows_item) @@ -471,6 +472,13 @@ compute_common_attribute(DefElem *defel, *security_item = defel; } + else if (strcmp(defel->defname, "leakproof") == 0) + { + if (*leakproof_item) + goto duplicate_error; + + *leakproof_item = defel; + } else if (strcmp(defel->defname, "set") == 0) { *set_items = lappend(*set_items, defel->arg); @@ -564,6 +572,7 @@ compute_attributes_sql_style(List *options, char *volatility_p, bool *strict_p, bool *security_definer, + bool *leakproof_p, ArrayType **proconfig, float4 *procost, float4 *prorows) @@ -575,6 +584,7 @@ compute_attributes_sql_style(List *options, DefElem *volatility_item = NULL; DefElem *strict_item = NULL; DefElem *security_item = NULL; + DefElem *leakproof_item = NULL; List *set_items = NIL; DefElem *cost_item = NULL; DefElem *rows_item = NULL; @@ -611,6 +621,7 @@ compute_attributes_sql_style(List *options, &volatility_item, &strict_item, &security_item, + &leakproof_item, &set_items, &cost_item, &rows_item)) @@ -653,6 +664,8 @@ compute_attributes_sql_style(List *options, *strict_p = intVal(strict_item->arg); if (security_item) *security_definer = intVal(security_item->arg); + if (leakproof_item) + *leakproof_p = intVal(leakproof_item->arg); if (set_items) *proconfig = update_proconfig_value(NULL, set_items); if (cost_item) @@ -805,7 +818,8 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString) Oid requiredResultType; bool isWindowFunc, isStrict, - security; + security, + isLeakProof; char volatility; ArrayType *proconfig; float4 procost; @@ -828,6 +842,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString) isWindowFunc = false; isStrict = false; security = false; + isLeakProof = false; volatility = PROVOLATILE_VOLATILE; proconfig = NULL; procost = -1; /* indicates not set */ @@ -837,7 +852,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString) compute_attributes_sql_style(stmt->options, &as_clause, &language, &isWindowFunc, &volatility, - &isStrict, &security, + &isStrict, &security, &isLeakProof, &proconfig, &procost, &prorows); /* Look up the language and validate permissions */ @@ -875,6 +890,16 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString) ReleaseSysCache(languageTuple); /* + * Only superuser is allowed to create leakproof functions because + * it possibly allows unprivileged users to reference invisible tuples + * to be filtered out using views for row-level security. + */ + if (isLeakProof && !superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("only superuser can define a leakproof function"))); + + /* * Convert remaining parameters of CREATE to form wanted by * ProcedureCreate. */ @@ -960,6 +985,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString) false, /* not an aggregate */ isWindowFunc, security, + isLeakProof, isStrict, volatility, parameterTypes, @@ -1238,6 +1264,7 @@ AlterFunction(AlterFunctionStmt *stmt) DefElem *volatility_item = NULL; DefElem *strict_item = NULL; DefElem *security_def_item = NULL; + DefElem *leakproof_item = NULL; List *set_items = NIL; DefElem *cost_item = NULL; DefElem *rows_item = NULL; @@ -1274,6 +1301,7 @@ AlterFunction(AlterFunctionStmt *stmt) &volatility_item, &strict_item, &security_def_item, + &leakproof_item, &set_items, &cost_item, &rows_item) == false) @@ -1286,6 +1314,14 @@ AlterFunction(AlterFunctionStmt *stmt) procForm->proisstrict = intVal(strict_item->arg); if (security_def_item) procForm->prosecdef = intVal(security_def_item->arg); + if (leakproof_item) + { + if (intVal(leakproof_item->arg) && !superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("only superuser can define a leakproof function"))); + procForm->proleakproof = intVal(leakproof_item->arg); + } if (cost_item) { procForm->procost = defGetNumeric(cost_item); diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c index a3a111d41a0..8d6a0416d5f 100644 --- a/src/backend/commands/proclang.c +++ b/src/backend/commands/proclang.c @@ -131,6 +131,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt) false, /* isAgg */ false, /* isWindowFunc */ false, /* security_definer */ + false, /* isLeakProof */ false, /* isStrict */ PROVOLATILE_VOLATILE, buildoidvector(funcargtypes, 0), @@ -166,6 +167,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt) false, /* isAgg */ false, /* isWindowFunc */ false, /* security_definer */ + false, /* isLeakProof */ true, /* isStrict */ PROVOLATILE_VOLATILE, buildoidvector(funcargtypes, 1), @@ -204,6 +206,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt) false, /* isAgg */ false, /* isWindowFunc */ false, /* security_definer */ + false, /* isLeakProof */ true, /* isStrict */ PROVOLATILE_VOLATILE, buildoidvector(funcargtypes, 1), diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index c6bc6c8e876..3523554cfbd 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -1521,6 +1521,7 @@ makeRangeConstructors(const char *name, Oid namespace, false, /* isAgg */ false, /* isWindowFunc */ false, /* security_definer */ + false, /* leakproof */ false, /* isStrict */ PROVOLATILE_IMMUTABLE, /* volatility */ constructorArgTypesVector, /* parameterTypes */ diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 19bf7340ad4..e99e4cc1761 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -1042,16 +1042,9 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); Node *clause = (Node *) rinfo->clause; - /* - * XXX. You might wonder why we're testing rte->security_barrier - * qual-by-qual here rather than hoisting the test up into the - * surrounding if statement; after all, the answer will be the - * same for all quals. The answer is that we expect to shortly - * change this logic to allow pushing down some quals that use only - * "leakproof" operators even through a security barrier. - */ if (!rinfo->pseudoconstant && - !rte->security_barrier && + (!rte->security_barrier || + !contain_leaky_functions(clause)) && qual_is_pushdown_safe(subquery, rti, clause, differentTypes)) { /* Push it down */ diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 126d49452e2..cd3da46bc5e 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -93,6 +93,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_leaky_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,145 @@ contain_nonstrict_functions_walker(Node *node, void *context) context); } +/***************************************************************************** + * Check clauses for non-leakproof functions + *****************************************************************************/ + +/* + * contain_leaky_functions + * Recursively search for leaky functions within a clause. + * + * Returns true if any function call with side-effect may be present in the + * clause. Qualifiers from outside the a security_barrier view should not + * be pushed down into the view, lest the contents of tuples intended to be + * filtered out be revealed via side effects. + */ +bool +contain_leaky_functions(Node *clause) +{ + return contain_leaky_functions_walker(clause, NULL); +} + +static bool +contain_leaky_functions_walker(Node *node, void *context) +{ + if (node == NULL) + return false; + + switch (nodeTag(node)) + { + case T_Var: + case T_Const: + case T_Param: + case T_ArrayExpr: + case T_NamedArgExpr: + case T_BoolExpr: + case T_RelabelType: + case T_CaseExpr: + case T_CaseTestExpr: + case T_RowExpr: + case T_MinMaxExpr: + case T_NullTest: + case T_BooleanTest: + case T_List: + /* + * We know these node types don't contain function calls; but + * something further down in the node tree might. + */ + break; + + case T_FuncExpr: + { + FuncExpr *expr = (FuncExpr *) node; + + if (!get_func_leakproof(expr->funcid)) + return true; + } + break; + + case T_OpExpr: + case T_DistinctExpr: /* struct-equivalent to OpExpr */ + case T_NullIfExpr: /* struct-equivalent to OpExpr */ + { + OpExpr *expr = (OpExpr *) node; + + set_opfuncid(expr); + if (!get_func_leakproof(expr->opfuncid)) + return true; + } + break; + + case T_ScalarArrayOpExpr: + { + ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node; + + set_sa_opfuncid(expr); + if (!get_func_leakproof(expr->opfuncid)) + return true; + } + break; + + case T_CoerceViaIO: + { + CoerceViaIO *expr = (CoerceViaIO *) node; + Oid funcid; + Oid ioparam; + bool varlena; + + getTypeInputInfo(exprType((Node *)expr->arg), + &funcid, &ioparam); + if (!get_func_leakproof(funcid)) + return true; + + getTypeOutputInfo(expr->resulttype, &funcid, &varlena); + if (!get_func_leakproof(funcid)) + return true; + } + break; + + case T_ArrayCoerceExpr: + { + ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node; + Oid funcid; + Oid ioparam; + bool varlena; + + getTypeInputInfo(exprType((Node *)expr->arg), + &funcid, &ioparam); + if (!get_func_leakproof(funcid)) + return true; + getTypeOutputInfo(expr->resulttype, &funcid, &varlena); + if (!get_func_leakproof(funcid)) + return true; + } + break; + + case T_RowCompareExpr: + { + RowCompareExpr *rcexpr = (RowCompareExpr *) node; + ListCell *opid; + + foreach(opid, rcexpr->opnos) + { + Oid funcid = get_opcode(lfirst_oid(opid)); + + if (!get_func_leakproof(funcid)) + return true; + } + } + break; + + default: + /* + * If we don't recognize the node tag, assume it might be leaky. + * This prevents an unexpected security hole if someone adds a new + * node type that can call a function. + */ + return true; + } + return expression_tree_walker(node, contain_leaky_functions_walker, + context); +} /* * find_nonnullable_rels diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index ac4b4635b94..d79576bcaa3 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -526,7 +526,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType, KEY - LABEL LANGUAGE LARGE_P LAST_P LC_COLLATE_P LC_CTYPE_P LEADING + LABEL LANGUAGE LARGE_P LAST_P LC_COLLATE_P LC_CTYPE_P LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P @@ -6115,6 +6115,10 @@ common_func_opt_item: { $$ = makeDefElem("security", (Node *)makeInteger(FALSE)); } + | LEAKPROOF + { + $$ = makeDefElem("leakproof", (Node *)makeInteger(TRUE)); + } | COST NumericOnly { $$ = makeDefElem("cost", (Node *)$2); @@ -12219,6 +12223,7 @@ unreserved_keyword: | LAST_P | LC_COLLATE_P | LC_CTYPE_P + | LEAKPROOF | LEVEL | LISTEN | LOAD diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index dccf380cc27..44dab822648 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -1548,6 +1548,25 @@ func_volatile(Oid funcid) } /* + * get_func_leakproof + * Given procedure id, return the function's leakproof field. + */ +bool +get_func_leakproof(Oid funcid) +{ + HeapTuple tp; + bool result; + + tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for function %u", funcid); + + result = ((Form_pg_proc) GETSTRUCT(tp))->proleakproof; + ReleaseSysCache(tp); + return result; +} + +/* * get_func_cost * Given procedure id, return the function's procost field. */ |