Skip to content

Commit

Permalink
Allow LEAKPROOF functions for better performance of security views.
Browse files Browse the repository at this point in the history
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
robertmhaas committed Feb 14, 2012
1 parent 2bbd88f commit cd30728
Show file tree
Hide file tree
Showing 28 changed files with 3,356 additions and 2,454 deletions.
12 changes: 12 additions & 0 deletions doc/src/sgml/catalogs.sgml
Expand Up @@ -4423,6 +4423,18 @@
function)</entry>
</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>
Expand Down
13 changes: 12 additions & 1 deletion doc/src/sgml/ref/alter_function.sgml
Expand Up @@ -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>
Expand Down Expand Up @@ -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>

Expand Down
19 changes: 18 additions & 1 deletion doc/src/sgml/ref/create_function.sgml
Expand Up @@ -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>
Expand Down Expand Up @@ -324,6 +324,23 @@ CREATE [ OR REPLACE ] FUNCTION
</listitem>
</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>
Expand Down
14 changes: 14 additions & 0 deletions doc/src/sgml/rules.sgml
Expand Up @@ -1890,6 +1890,20 @@ CREATE VIEW phone_number WITH (security_barrier) AS
enabled by default.
</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
Expand Down
1 change: 1 addition & 0 deletions src/backend/catalog/pg_aggregate.c
Expand Up @@ -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) */
Expand Down
2 changes: 2 additions & 0 deletions src/backend/catalog/pg_proc.c
Expand Up @@ -76,6 +76,7 @@ ProcedureCreate(const char *procedureName,
bool isAgg,
bool isWindowFunc,
bool security_definer,
bool isLeakProof,
bool isStrict,
char volatility,
oidvector *parameterTypes,
Expand Down Expand Up @@ -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);
Expand Down
40 changes: 38 additions & 2 deletions src/backend/commands/functioncmds.c
Expand Up @@ -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)
Expand All @@ -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);
Expand Down Expand Up @@ -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)
Expand All @@ -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;
Expand Down Expand Up @@ -611,6 +621,7 @@ compute_attributes_sql_style(List *options,
&volatility_item,
&strict_item,
&security_item,
&leakproof_item,
&set_items,
&cost_item,
&rows_item))
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -805,7 +818,8 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
Oid requiredResultType;
bool isWindowFunc,
isStrict,
security;
security,
isLeakProof;
char volatility;
ArrayType *proconfig;
float4 procost;
Expand All @@ -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 */
Expand All @@ -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 */
Expand Down Expand Up @@ -874,6 +889,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.
Expand Down Expand Up @@ -960,6 +985,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
false, /* not an aggregate */
isWindowFunc,
security,
isLeakProof,
isStrict,
volatility,
parameterTypes,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1274,6 +1301,7 @@ AlterFunction(AlterFunctionStmt *stmt)
&volatility_item,
&strict_item,
&security_def_item,
&leakproof_item,
&set_items,
&cost_item,
&rows_item) == false)
Expand All @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions src/backend/commands/proclang.c
Expand Up @@ -131,6 +131,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
false, /* isAgg */
false, /* isWindowFunc */
false, /* security_definer */
false, /* isLeakProof */
false, /* isStrict */
PROVOLATILE_VOLATILE,
buildoidvector(funcargtypes, 0),
Expand Down Expand Up @@ -166,6 +167,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
false, /* isAgg */
false, /* isWindowFunc */
false, /* security_definer */
false, /* isLeakProof */
true, /* isStrict */
PROVOLATILE_VOLATILE,
buildoidvector(funcargtypes, 1),
Expand Down Expand Up @@ -204,6 +206,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
false, /* isAgg */
false, /* isWindowFunc */
false, /* security_definer */
false, /* isLeakProof */
true, /* isStrict */
PROVOLATILE_VOLATILE,
buildoidvector(funcargtypes, 1),
Expand Down
1 change: 1 addition & 0 deletions src/backend/commands/typecmds.c
Expand Up @@ -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 */
Expand Down
11 changes: 2 additions & 9 deletions src/backend/optimizer/path/allpaths.c
Expand Up @@ -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 */
Expand Down

0 comments on commit cd30728

Please sign in to comment.