1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-05 07:21:24 +03:00

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.
This commit is contained in:
Stephen Frost
2015-04-27 12:29:42 -04:00
parent 06ca28d5ab
commit dcbf5948e1
10 changed files with 320 additions and 39 deletions

View File

@ -85,13 +85,11 @@ SET ROLE s1;
-- restrictive hook's policy is current_user = superuser
-- combined with AND, results in nothing being allowed
EXPLAIN (costs off) SELECT * FROM rls_test_both;
QUERY PLAN
-------------------------------------------------------
Subquery Scan on rls_test_both
Filter: ("current_user"() = rls_test_both.username)
-> Seq Scan on rls_test_both rls_test_both_1
Filter: ("current_user"() = supervisor)
(4 rows)
QUERY PLAN
-------------------------------------------------------------------------------
Seq Scan on rls_test_both
Filter: ((supervisor = "current_user"()) AND (username = "current_user"()))
(2 rows)
SELECT * FROM rls_test_both;
username | supervisor | data

View File

@ -1913,6 +1913,112 @@ EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak(b);
Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))
(4 rows)
--
-- Qual push-down of leaky functions, when not referring to table
--
SELECT * FROM y2 WHERE f_leak('abc');
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
NOTICE: f_leak => abc
a | b
----+----------------------------------
0 | cfcd208495d565ef66e7dff9f98764da
2 | c81e728d9d4c2f636f067f89cc14862c
3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
4 | a87ff679a2f3e71d9181a67b7542122c
6 | 1679091c5a880faf6fb5e6087eb1b2dc
8 | c9f0f895fb98ab9159f51fd0297e236d
9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
10 | d3d9446802a44259755d38e6d163e820
12 | c20ad4d76fe97759aa27a0c99bff6710
14 | aab3238922bcc25a6f606eb525ffdc56
15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
16 | c74d97b01eae257e44aa9d5bade97baf
18 | 6f4922f45568161a8cdf4ad2299f6d23
20 | 98f13708210194c475687be6106a3b84
(14 rows)
EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc');
QUERY PLAN
---------------------------------------------------------------------------------------
Seq Scan on y2
Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))
(2 rows)
CREATE TABLE test_qual_pushdown (
abc text
);
INSERT INTO test_qual_pushdown VALUES ('abc'),('def');
SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);
NOTICE: f_leak => abc
NOTICE: f_leak => def
a | b | abc
---+---+-----
(0 rows)
EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);
QUERY PLAN
-------------------------------------------------------------------------
Hash Join
Hash Cond: (test_qual_pushdown.abc = y2.b)
-> Seq Scan on test_qual_pushdown
Filter: f_leak(abc)
-> Hash
-> Seq Scan on y2
Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))
(7 rows)
SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);
NOTICE: f_leak => cfcd208495d565ef66e7dff9f98764da
NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c
NOTICE: f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3
NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d
NOTICE: f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26
NOTICE: f_leak => d3d9446802a44259755d38e6d163e820
NOTICE: f_leak => c20ad4d76fe97759aa27a0c99bff6710
NOTICE: f_leak => aab3238922bcc25a6f606eb525ffdc56
NOTICE: f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3
NOTICE: f_leak => c74d97b01eae257e44aa9d5bade97baf
NOTICE: f_leak => 6f4922f45568161a8cdf4ad2299f6d23
NOTICE: f_leak => 98f13708210194c475687be6106a3b84
a | b | abc
---+---+-----
(0 rows)
EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);
QUERY PLAN
-------------------------------------------------------------------------------
Hash Join
Hash Cond: (test_qual_pushdown.abc = y2.b)
-> Seq Scan on test_qual_pushdown
-> Hash
-> Subquery Scan on y2
Filter: f_leak(y2.b)
-> Seq Scan on y2 y2_1
Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))
(8 rows)
DROP TABLE test_qual_pushdown;
--
-- Plancache invalidate on user change.
--

View File

@ -1348,6 +1348,52 @@ EXPLAIN (COSTS OFF) SELECT * FROM my_property_secure WHERE f_leak(passwd);
Filter: (name = ("current_user"())::text)
(4 rows)
--
-- scenario: qualifiers can be pushed down if they contain leaky functions,
-- provided they aren't passed data from inside the view.
--
SELECT * FROM my_property_normal v
WHERE f_leak('passwd') AND f_leak(passwd);
NOTICE: f_leak => passwd
NOTICE: f_leak => passwd123
NOTICE: f_leak => passwd
NOTICE: f_leak => beafsteak
NOTICE: f_leak => passwd
NOTICE: f_leak => hamburger
cid | name | tel | passwd
-----+---------------+------------------+-----------
101 | regress_alice | +81-12-3456-7890 | passwd123
(1 row)
EXPLAIN (COSTS OFF) SELECT * FROM my_property_normal v
WHERE f_leak('passwd') AND f_leak(passwd);
QUERY PLAN
---------------------------------------------------------------------------------------------
Seq Scan on customer
Filter: (f_leak('passwd'::text) AND f_leak(passwd) AND (name = ("current_user"())::text))
(2 rows)
SELECT * FROM my_property_secure v
WHERE f_leak('passwd') AND f_leak(passwd);
NOTICE: f_leak => passwd
NOTICE: f_leak => passwd123
NOTICE: f_leak => passwd
NOTICE: f_leak => passwd
cid | name | tel | passwd
-----+---------------+------------------+-----------
101 | regress_alice | +81-12-3456-7890 | passwd123
(1 row)
EXPLAIN (COSTS OFF) SELECT * FROM my_property_secure v
WHERE f_leak('passwd') AND f_leak(passwd);
QUERY PLAN
--------------------------------------------------------------------------------
Subquery Scan on v
Filter: f_leak(v.passwd)
-> Seq Scan on customer
Filter: (f_leak('passwd'::text) AND (name = ("current_user"())::text))
(4 rows)
--
-- scenario: if a qualifier references only one-side of a particular join-
-- tree, it shall be distributed to the most deep scan plan as

View File

@ -1348,6 +1348,52 @@ EXPLAIN (COSTS OFF) SELECT * FROM my_property_secure WHERE f_leak(passwd);
Filter: (name = ("current_user"())::text)
(4 rows)
--
-- scenario: qualifiers can be pushed down if they contain leaky functions,
-- provided they aren't passed data from inside the view.
--
SELECT * FROM my_property_normal v
WHERE f_leak('passwd') AND f_leak(passwd);
NOTICE: f_leak => passwd
NOTICE: f_leak => passwd123
NOTICE: f_leak => passwd
NOTICE: f_leak => beafsteak
NOTICE: f_leak => passwd
NOTICE: f_leak => hamburger
cid | name | tel | passwd
-----+---------------+------------------+-----------
101 | regress_alice | +81-12-3456-7890 | passwd123
(1 row)
EXPLAIN (COSTS OFF) SELECT * FROM my_property_normal v
WHERE f_leak('passwd') AND f_leak(passwd);
QUERY PLAN
---------------------------------------------------------------------------------------------
Seq Scan on customer
Filter: (f_leak('passwd'::text) AND f_leak(passwd) AND (name = ("current_user"())::text))
(2 rows)
SELECT * FROM my_property_secure v
WHERE f_leak('passwd') AND f_leak(passwd);
NOTICE: f_leak => passwd
NOTICE: f_leak => passwd123
NOTICE: f_leak => passwd
NOTICE: f_leak => passwd
cid | name | tel | passwd
-----+---------------+------------------+-----------
101 | regress_alice | +81-12-3456-7890 | passwd123
(1 row)
EXPLAIN (COSTS OFF) SELECT * FROM my_property_secure v
WHERE f_leak('passwd') AND f_leak(passwd);
QUERY PLAN
--------------------------------------------------------------------------------
Subquery Scan on v
Filter: f_leak(v.passwd)
-> Seq Scan on customer
Filter: (f_leak('passwd'::text) AND (name = ("current_user"())::text))
(4 rows)
--
-- scenario: if a qualifier references only one-side of a particular join-
-- tree, it shall be distributed to the most deep scan plan as

View File

@ -679,6 +679,26 @@ SET SESSION AUTHORIZATION rls_regress_user1;
SELECT * FROM y2 WHERE f_leak(b);
EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak(b);
--
-- Qual push-down of leaky functions, when not referring to table
--
SELECT * FROM y2 WHERE f_leak('abc');
EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc');
CREATE TABLE test_qual_pushdown (
abc text
);
INSERT INTO test_qual_pushdown VALUES ('abc'),('def');
SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);
EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);
SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);
EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);
DROP TABLE test_qual_pushdown;
--
-- Plancache invalidate on user change.
--

View File

@ -95,6 +95,20 @@ EXPLAIN (COSTS OFF) SELECT * FROM my_property_normal WHERE f_leak(passwd);
SELECT * FROM my_property_secure WHERE f_leak(passwd);
EXPLAIN (COSTS OFF) SELECT * FROM my_property_secure WHERE f_leak(passwd);
--
-- scenario: qualifiers can be pushed down if they contain leaky functions,
-- provided they aren't passed data from inside the view.
--
SELECT * FROM my_property_normal v
WHERE f_leak('passwd') AND f_leak(passwd);
EXPLAIN (COSTS OFF) SELECT * FROM my_property_normal v
WHERE f_leak('passwd') AND f_leak(passwd);
SELECT * FROM my_property_secure v
WHERE f_leak('passwd') AND f_leak(passwd);
EXPLAIN (COSTS OFF) SELECT * FROM my_property_secure v
WHERE f_leak('passwd') AND f_leak(passwd);
--
-- scenario: if a qualifier references only one-side of a particular join-
-- tree, it shall be distributed to the most deep scan plan as