Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit dcbf594

Browse files
committed
Improve qual pushdown for RLS and SB views
The original security barrier view implementation, on which RLS is built, prevented all non-leakproof functions from being pushed down to below the view, even when the function was not receiving any data from the view. This optimization improves on that situation by, instead of checking strictly for non-leakproof functions, it checks for Vars being passed to non-leakproof functions and allows functions which do not accept arguments or whose arguments are not from the current query level (eg: constants can be particularly useful) to be pushed down. As discussed, this does mean that a function which is pushed down might gain some idea that there are rows meeting a certain criteria based on the number of times the function is called, but this isn't a particularly new issue and the documentation in rules.sgml already addressed similar covert-channel risks. That documentation is updated to reflect that non-leakproof functions may be pushed down now, if they meet the above-described criteria. Author: Dean Rasheed, with a bit of rework to make things clearer, along with comment and documentation updates from me.
1 parent 06ca28d commit dcbf594

File tree

10 files changed

+320
-39
lines changed

10 files changed

+320
-39
lines changed

doc/src/sgml/rules.sgml

+7-4
Original file line numberDiff line numberDiff line change
@@ -2136,7 +2136,7 @@ SELECT * FROM phone_number WHERE tricky(person, phone);
21362136
When it is necessary for a view to provide row level security, the
21372137
<literal>security_barrier</literal> attribute should be applied to
21382138
the view. This prevents maliciously-chosen functions and operators from
2139-
being invoked on rows until after the view has done its work. For
2139+
being passed values from rows until after the view has done its work. For
21402140
example, if the view shown above had been created like this, it would
21412141
be secure:
21422142
<programlisting>
@@ -2157,9 +2157,12 @@ CREATE VIEW phone_number WITH (security_barrier) AS
21572157
operators. The query planner can safely allow such functions to be evaluated
21582158
at any point in the query execution process, since invoking them on rows
21592159
invisible to the user will not leak any information about the unseen rows.
2160-
In contrast, a function that might throw an error depending on the values
2161-
received as arguments (such as one that throws an error in the event of
2162-
overflow or division by zero) are not leak-proof, and could provide
2160+
Further, functions which do not take arguments or which are not passed any
2161+
arguments from the security barrier view do not have to be marked as
2162+
<literal>LEAKPROOF</literal> to be pushed down, as they never receive data
2163+
from the view. In contrast, a function that might throw an error depending
2164+
on the values received as arguments (such as one that throws an error in the
2165+
event of overflow or division by zero) are not leak-proof, and could provide
21632166
significant information about the unseen rows if applied before the security
21642167
view's row filters.
21652168
</para>

src/backend/optimizer/path/allpaths.c

+4-2
Original file line numberDiff line numberDiff line change
@@ -1982,7 +1982,9 @@ targetIsInAllPartitionLists(TargetEntry *tle, Query *query)
19821982
* 2. If unsafeVolatile is set, the qual must not contain any volatile
19831983
* functions.
19841984
*
1985-
* 3. If unsafeLeaky is set, the qual must not contain any leaky functions.
1985+
* 3. If unsafeLeaky is set, the qual must not contain any leaky functions
1986+
* that are passed Var nodes, and therefore might reveal values from the
1987+
* subquery as side effects.
19861988
*
19871989
* 4. The qual must not refer to the whole-row output of the subquery
19881990
* (since there is no easy way to name that within the subquery itself).
@@ -2009,7 +2011,7 @@ qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual,
20092011

20102012
/* Refuse leaky quals if told to (point 3) */
20112013
if (safetyInfo->unsafeLeaky &&
2012-
contain_leaky_functions(qual))
2014+
contain_leaked_vars(qual))
20132015
return false;
20142016

20152017
/*

src/backend/optimizer/util/clauses.c

+71-25
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ static bool contain_mutable_functions_walker(Node *node, void *context);
9797
static bool contain_volatile_functions_walker(Node *node, void *context);
9898
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
9999
static bool contain_nonstrict_functions_walker(Node *node, void *context);
100-
static bool contain_leaky_functions_walker(Node *node, void *context);
100+
static bool contain_leaked_vars_walker(Node *node, void *context);
101101
static Relids find_nonnullable_rels_walker(Node *node, bool top_level);
102102
static List *find_nonnullable_vars_walker(Node *node, bool top_level);
103103
static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
@@ -1318,26 +1318,30 @@ contain_nonstrict_functions_walker(Node *node, void *context)
13181318
}
13191319

13201320
/*****************************************************************************
1321-
* Check clauses for non-leakproof functions
1321+
* Check clauses for Vars passed to non-leakproof functions
13221322
*****************************************************************************/
13231323

13241324
/*
1325-
* contain_leaky_functions
1326-
* Recursively search for leaky functions within a clause.
1325+
* contain_leaked_vars
1326+
* Recursively scan a clause to discover whether it contains any Var
1327+
* nodes (of the current query level) that are passed as arguments to
1328+
* leaky functions.
13271329
*
1328-
* Returns true if any function call with side-effect may be present in the
1329-
* clause. Qualifiers from outside the a security_barrier view should not
1330-
* be pushed down into the view, lest the contents of tuples intended to be
1331-
* filtered out be revealed via side effects.
1330+
* Returns true if the clause contains any non-leakproof functions that are
1331+
* passed Var nodes of the current query level, and which might therefore leak
1332+
* data. Qualifiers from outside a security_barrier view that might leak data
1333+
* in this way should not be pushed down into the view in case the contents of
1334+
* tuples intended to be filtered out by the view are revealed by the leaky
1335+
* functions.
13321336
*/
13331337
bool
1334-
contain_leaky_functions(Node *clause)
1338+
contain_leaked_vars(Node *clause)
13351339
{
1336-
return contain_leaky_functions_walker(clause, NULL);
1340+
return contain_leaked_vars_walker(clause, NULL);
13371341
}
13381342

13391343
static bool
1340-
contain_leaky_functions_walker(Node *node, void *context)
1344+
contain_leaked_vars_walker(Node *node, void *context)
13411345
{
13421346
if (node == NULL)
13431347
return false;
@@ -1369,7 +1373,8 @@ contain_leaky_functions_walker(Node *node, void *context)
13691373
{
13701374
FuncExpr *expr = (FuncExpr *) node;
13711375

1372-
if (!get_func_leakproof(expr->funcid))
1376+
if (!get_func_leakproof(expr->funcid) &&
1377+
contain_var_clause((Node *) expr->args))
13731378
return true;
13741379
}
13751380
break;
@@ -1381,7 +1386,8 @@ contain_leaky_functions_walker(Node *node, void *context)
13811386
OpExpr *expr = (OpExpr *) node;
13821387

13831388
set_opfuncid(expr);
1384-
if (!get_func_leakproof(expr->opfuncid))
1389+
if (!get_func_leakproof(expr->opfuncid) &&
1390+
contain_var_clause((Node *) expr->args))
13851391
return true;
13861392
}
13871393
break;
@@ -1391,7 +1397,8 @@ contain_leaky_functions_walker(Node *node, void *context)
13911397
ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
13921398

13931399
set_sa_opfuncid(expr);
1394-
if (!get_func_leakproof(expr->opfuncid))
1400+
if (!get_func_leakproof(expr->opfuncid) &&
1401+
contain_var_clause((Node *) expr->args))
13951402
return true;
13961403
}
13971404
break;
@@ -1401,15 +1408,29 @@ contain_leaky_functions_walker(Node *node, void *context)
14011408
CoerceViaIO *expr = (CoerceViaIO *) node;
14021409
Oid funcid;
14031410
Oid ioparam;
1411+
bool leakproof;
14041412
bool varlena;
14051413

1414+
/*
1415+
* Data may be leaked if either the input or the output
1416+
* function is leaky.
1417+
*/
14061418
getTypeInputInfo(exprType((Node *) expr->arg),
14071419
&funcid, &ioparam);
1408-
if (!get_func_leakproof(funcid))
1409-
return true;
1420+
leakproof = get_func_leakproof(funcid);
1421+
1422+
/*
1423+
* If the input function is leakproof, then check the output
1424+
* function.
1425+
*/
1426+
if (leakproof)
1427+
{
1428+
getTypeOutputInfo(expr->resulttype, &funcid, &varlena);
1429+
leakproof = get_func_leakproof(funcid);
1430+
}
14101431

1411-
getTypeOutputInfo(expr->resulttype, &funcid, &varlena);
1412-
if (!get_func_leakproof(funcid))
1432+
if (!leakproof &&
1433+
contain_var_clause((Node *) expr->arg))
14131434
return true;
14141435
}
14151436
break;
@@ -1419,14 +1440,29 @@ contain_leaky_functions_walker(Node *node, void *context)
14191440
ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node;
14201441
Oid funcid;
14211442
Oid ioparam;
1443+
bool leakproof;
14221444
bool varlena;
14231445

1446+
/*
1447+
* Data may be leaked if either the input or the output
1448+
* function is leaky.
1449+
*/
14241450
getTypeInputInfo(exprType((Node *) expr->arg),
14251451
&funcid, &ioparam);
1426-
if (!get_func_leakproof(funcid))
1427-
return true;
1428-
getTypeOutputInfo(expr->resulttype, &funcid, &varlena);
1429-
if (!get_func_leakproof(funcid))
1452+
leakproof = get_func_leakproof(funcid);
1453+
1454+
/*
1455+
* If the input function is leakproof, then check the output
1456+
* function.
1457+
*/
1458+
if (leakproof)
1459+
{
1460+
getTypeOutputInfo(expr->resulttype, &funcid, &varlena);
1461+
leakproof = get_func_leakproof(funcid);
1462+
}
1463+
1464+
if (!leakproof &&
1465+
contain_var_clause((Node *) expr->arg))
14301466
return true;
14311467
}
14321468
break;
@@ -1435,12 +1471,22 @@ contain_leaky_functions_walker(Node *node, void *context)
14351471
{
14361472
RowCompareExpr *rcexpr = (RowCompareExpr *) node;
14371473
ListCell *opid;
1474+
ListCell *larg;
1475+
ListCell *rarg;
14381476

1439-
foreach(opid, rcexpr->opnos)
1477+
/*
1478+
* Check the comparison function and arguments passed to it for
1479+
* each pair of row elements.
1480+
*/
1481+
forthree(opid, rcexpr->opnos,
1482+
larg, rcexpr->largs,
1483+
rarg, rcexpr->rargs)
14401484
{
14411485
Oid funcid = get_opcode(lfirst_oid(opid));
14421486

1443-
if (!get_func_leakproof(funcid))
1487+
if (!get_func_leakproof(funcid) &&
1488+
(contain_var_clause((Node *) lfirst(larg)) ||
1489+
contain_var_clause((Node *) lfirst(rarg))))
14441490
return true;
14451491
}
14461492
}
@@ -1455,7 +1501,7 @@ contain_leaky_functions_walker(Node *node, void *context)
14551501
*/
14561502
return true;
14571503
}
1458-
return expression_tree_walker(node, contain_leaky_functions_walker,
1504+
return expression_tree_walker(node, contain_leaked_vars_walker,
14591505
context);
14601506
}
14611507

src/include/optimizer/clauses.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ extern bool contain_mutable_functions(Node *clause);
6363
extern bool contain_volatile_functions(Node *clause);
6464
extern bool contain_volatile_functions_not_nextval(Node *clause);
6565
extern bool contain_nonstrict_functions(Node *clause);
66-
extern bool contain_leaky_functions(Node *clause);
66+
extern bool contain_leaked_vars(Node *clause);
6767

6868
extern Relids find_nonnullable_rels(Node *clause);
6969
extern List *find_nonnullable_vars(Node *clause);

src/test/modules/test_rls_hooks/expected/test_rls_hooks.out

+5-7
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,11 @@ SET ROLE s1;
8585
-- restrictive hook's policy is current_user = superuser
8686
-- combined with AND, results in nothing being allowed
8787
EXPLAIN (costs off) SELECT * FROM rls_test_both;
88-
QUERY PLAN
89-
-------------------------------------------------------
90-
Subquery Scan on rls_test_both
91-
Filter: ("current_user"() = rls_test_both.username)
92-
-> Seq Scan on rls_test_both rls_test_both_1
93-
Filter: ("current_user"() = supervisor)
94-
(4 rows)
88+
QUERY PLAN
89+
-------------------------------------------------------------------------------
90+
Seq Scan on rls_test_both
91+
Filter: ((supervisor = "current_user"()) AND (username = "current_user"()))
92+
(2 rows)
9593

9694
SELECT * FROM rls_test_both;
9795
username | supervisor | data

src/test/regress/expected/rowsecurity.out

+106
Original file line numberDiff line numberDiff line change
@@ -1913,6 +1913,112 @@ EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak(b);
19131913
Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))
19141914
(4 rows)
19151915

1916+
--
1917+
-- Qual push-down of leaky functions, when not referring to table
1918+
--
1919+
SELECT * FROM y2 WHERE f_leak('abc');
1920+
NOTICE: f_leak => abc
1921+
NOTICE: f_leak => abc
1922+
NOTICE: f_leak => abc
1923+
NOTICE: f_leak => abc
1924+
NOTICE: f_leak => abc
1925+
NOTICE: f_leak => abc
1926+
NOTICE: f_leak => abc
1927+
NOTICE: f_leak => abc
1928+
NOTICE: f_leak => abc
1929+
NOTICE: f_leak => abc
1930+
NOTICE: f_leak => abc
1931+
NOTICE: f_leak => abc
1932+
NOTICE: f_leak => abc
1933+
NOTICE: f_leak => abc
1934+
NOTICE: f_leak => abc
1935+
NOTICE: f_leak => abc
1936+
NOTICE: f_leak => abc
1937+
NOTICE: f_leak => abc
1938+
NOTICE: f_leak => abc
1939+
NOTICE: f_leak => abc
1940+
NOTICE: f_leak => abc
1941+
a | b
1942+
----+----------------------------------
1943+
0 | cfcd208495d565ef66e7dff9f98764da
1944+
2 | c81e728d9d4c2f636f067f89cc14862c
1945+
3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
1946+
4 | a87ff679a2f3e71d9181a67b7542122c
1947+
6 | 1679091c5a880faf6fb5e6087eb1b2dc
1948+
8 | c9f0f895fb98ab9159f51fd0297e236d
1949+
9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
1950+
10 | d3d9446802a44259755d38e6d163e820
1951+
12 | c20ad4d76fe97759aa27a0c99bff6710
1952+
14 | aab3238922bcc25a6f606eb525ffdc56
1953+
15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
1954+
16 | c74d97b01eae257e44aa9d5bade97baf
1955+
18 | 6f4922f45568161a8cdf4ad2299f6d23
1956+
20 | 98f13708210194c475687be6106a3b84
1957+
(14 rows)
1958+
1959+
EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc');
1960+
QUERY PLAN
1961+
---------------------------------------------------------------------------------------
1962+
Seq Scan on y2
1963+
Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))
1964+
(2 rows)
1965+
1966+
CREATE TABLE test_qual_pushdown (
1967+
abc text
1968+
);
1969+
INSERT INTO test_qual_pushdown VALUES ('abc'),('def');
1970+
SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);
1971+
NOTICE: f_leak => abc
1972+
NOTICE: f_leak => def
1973+
a | b | abc
1974+
---+---+-----
1975+
(0 rows)
1976+
1977+
EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);
1978+
QUERY PLAN
1979+
-------------------------------------------------------------------------
1980+
Hash Join
1981+
Hash Cond: (test_qual_pushdown.abc = y2.b)
1982+
-> Seq Scan on test_qual_pushdown
1983+
Filter: f_leak(abc)
1984+
-> Hash
1985+
-> Seq Scan on y2
1986+
Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))
1987+
(7 rows)
1988+
1989+
SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);
1990+
NOTICE: f_leak => cfcd208495d565ef66e7dff9f98764da
1991+
NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c
1992+
NOTICE: f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3
1993+
NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
1994+
NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
1995+
NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d
1996+
NOTICE: f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26
1997+
NOTICE: f_leak => d3d9446802a44259755d38e6d163e820
1998+
NOTICE: f_leak => c20ad4d76fe97759aa27a0c99bff6710
1999+
NOTICE: f_leak => aab3238922bcc25a6f606eb525ffdc56
2000+
NOTICE: f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3
2001+
NOTICE: f_leak => c74d97b01eae257e44aa9d5bade97baf
2002+
NOTICE: f_leak => 6f4922f45568161a8cdf4ad2299f6d23
2003+
NOTICE: f_leak => 98f13708210194c475687be6106a3b84
2004+
a | b | abc
2005+
---+---+-----
2006+
(0 rows)
2007+
2008+
EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);
2009+
QUERY PLAN
2010+
-------------------------------------------------------------------------------
2011+
Hash Join
2012+
Hash Cond: (test_qual_pushdown.abc = y2.b)
2013+
-> Seq Scan on test_qual_pushdown
2014+
-> Hash
2015+
-> Subquery Scan on y2
2016+
Filter: f_leak(y2.b)
2017+
-> Seq Scan on y2 y2_1
2018+
Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))
2019+
(8 rows)
2020+
2021+
DROP TABLE test_qual_pushdown;
19162022
--
19172023
-- Plancache invalidate on user change.
19182024
--

0 commit comments

Comments
 (0)