Permalink
Browse files

Allow LEAKPROOF functions for better performance of security views.

We don't normally allow quals to be pushed down into a view created
with the security_barrier option, but functions without side effects
are an exception: they're OK.  This allows much better performance in
common cases, such as when using an equality operator (that might
even be indexable).

There is an outstanding issue here with the CREATE FUNCTION / ALTER
FUNCTION syntax: there's no way to use ALTER FUNCTION to unset the
leakproof flag.  But I'm committing this as-is so that it doesn't
have to be rebased again; we can fix up the grammar in a future
commit.

KaiGai Kohei, with some wordsmithing by me.
  • Loading branch information...
1 parent 2bbd88f commit cd30728fb2ed7c367d545fc14ab850b5fa2a4850 Robert Haas committed Feb 14, 2012
View
@@ -4424,6 +4424,18 @@
</row>
<row>
+ <entry><structfield>proleakproof</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>
+ The function has no side effects. No information about the
+ arguments is conveyed except via the return value. Any function
+ that might throw an error depending on the values of its arguments
+ is not leakproof.
+ </entry>
+ </row>
+
+ <row>
<entry><structfield>proisstrict</structfield></entry>
<entry><type>bool</type></entry>
<entry></entry>
@@ -33,7 +33,7 @@ ALTER FUNCTION <replaceable>name</replaceable> ( [ [ <replaceable class="paramet
<phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase>
CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
- IMMUTABLE | STABLE | VOLATILE
+ IMMUTABLE | STABLE | VOLATILE | LEAKPROOF
[ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
COST <replaceable class="parameter">execution_cost</replaceable>
ROWS <replaceable class="parameter">result_rows</replaceable>
@@ -191,6 +191,17 @@ ALTER FUNCTION <replaceable>name</replaceable> ( [ [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>LEAKPROOF</literal></term>
+ <listitem>
+ <para>
+ Change whether the function is considered leakproof or not.
+ See <xref linkend="sql-createfunction"> for more information about
+ this capability.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>COST</literal> <replaceable class="parameter">execution_cost</replaceable></term>
@@ -26,7 +26,7 @@ CREATE [ OR REPLACE ] FUNCTION
| RETURNS TABLE ( <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">column_type</replaceable> [, ...] ) ]
{ LANGUAGE <replaceable class="parameter">lang_name</replaceable>
| WINDOW
- | IMMUTABLE | STABLE | VOLATILE
+ | IMMUTABLE | STABLE | VOLATILE | LEAKPROOF
| CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
| [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
| COST <replaceable class="parameter">execution_cost</replaceable>
@@ -325,6 +325,23 @@ CREATE [ OR REPLACE ] FUNCTION
</varlistentry>
<varlistentry>
+ <term><literal>LEAKPROOF</literal></term>
+ <listitem>
+ <para>
+ <literal>LEAKPROOF</literal> indicates that the function has no side
+ effects. It reveals no information about its arguments other than by
+ its return value. For example, a function which throws an error message
+ for some argument values but not others, or which includes the argument
+ values in any error message, is not leakproof. The query planner may
+ push leakproof functions (but not others) into views created with the
+ <literal>security_barrier</literal> option. See
+ <xref linkend="sql-createview"> and <xref linkend="rules-privileges">.
+ This option can only be set by the superuser.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>CALLED ON NULL INPUT</literal></term>
<term><literal>RETURNS NULL ON NULL INPUT</literal></term>
<term><literal>STRICT</literal></term>
View
@@ -1891,6 +1891,20 @@ CREATE VIEW phone_number WITH (security_barrier) AS
</para>
<para>
+ The query planner has more flexibility when dealing with functions that
+ have no side effects. Such functions are referred to as LEAKPROOF, and
+ include many simple, commonly used operators, such as many equality
+ operators. The query planner can safely allow such functions to be evaluated
+ at any point in the query execution process, since invoking them on rows
+ invisible to the user will not leak any information about the unseen rows.
+ In contrast, a function that might throw an error depending on the values
+ received as arguments (such as one that throws an error in the event of
+ overflow or division by zero) are not leak-proof, and could provide
+ significant information about the unseen rows if applied before the security
+ view's row filters.
+</para>
+
+<para>
It is important to understand that even a view created with the
<literal>security_barrier</literal> option is intended to be secure only
in the limited sense that the contents of the invisible tuples will not be
@@ -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) */
@@ -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);
@@ -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);
@@ -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),
@@ -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 */
@@ -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 */
Oops, something went wrong.

0 comments on commit cd30728

Please sign in to comment.