diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2017-01-18 12:58:20 -0500 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2017-01-18 12:58:20 -0500 |
commit | 215b43cdc8d6b4a1700886a39df1ee735cb0274d (patch) | |
tree | 793e79c1b1444b09776e3b7d61c80e0244bab088 /src/test | |
parent | aa17c06fb58533d09c79c68a4d34a6f56687ee38 (diff) | |
download | postgresql-215b43cdc8d6b4a1700886a39df1ee735cb0274d.tar.gz postgresql-215b43cdc8d6b4a1700886a39df1ee735cb0274d.zip |
Improve RLS planning by marking individual quals with security levels.
In an RLS query, we must ensure that security filter quals are evaluated
before ordinary query quals, in case the latter contain "leaky" functions
that could expose the contents of sensitive rows. The original
implementation of RLS planning ensured this by pushing the scan of a
secured table into a sub-query that it marked as a security-barrier view.
Unfortunately this results in very inefficient plans in many cases, because
the sub-query cannot be flattened and gets planned independently of the
rest of the query.
To fix, drop the use of sub-queries to enforce RLS qual order, and instead
mark each qual (RestrictInfo) with a security_level field establishing its
priority for evaluation. Quals must be evaluated in security_level order,
except that "leakproof" quals can be allowed to go ahead of quals of lower
security_level, if it's helpful to do so. This has to be enforced within
the ordering of any one list of quals to be evaluated at a table scan node,
and we also have to ensure that quals are not chosen for early evaluation
(i.e., use as an index qual or TID scan qual) if they're not allowed to go
ahead of other quals at the scan node.
This is sufficient to fix the problem for RLS quals, since we only support
RLS policies on simple tables and thus RLS quals will always exist at the
table scan level only. Eventually these qual ordering rules should be
enforced for join quals as well, which would permit improving planning for
explicit security-barrier views; but that's a task for another patch.
Note that FDWs would need to be aware of these rules --- and not, for
example, send an insecure qual for remote execution --- but since we do
not yet allow RLS policies on foreign tables, the case doesn't arise.
This will need to be addressed before we can allow such policies.
Patch by me, reviewed by Stephen Frost and Dean Rasheed.
Discussion: https://postgr.es/m/8185.1477432701@sss.pgh.pa.us
Diffstat (limited to 'src/test')
-rw-r--r-- | src/test/modules/test_rls_hooks/expected/test_rls_hooks.out | 24 | ||||
-rw-r--r-- | src/test/regress/expected/equivclass.out | 42 | ||||
-rw-r--r-- | src/test/regress/expected/rowsecurity.out | 877 | ||||
-rw-r--r-- | src/test/regress/expected/updatable_views.out | 304 | ||||
-rw-r--r-- | src/test/regress/sql/equivclass.sql | 32 | ||||
-rw-r--r-- | src/test/regress/sql/updatable_views.sql | 4 |
6 files changed, 543 insertions, 740 deletions
diff --git a/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out b/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out index 19284c18d48..b8c6d385814 100644 --- a/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out +++ b/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out @@ -145,13 +145,11 @@ ERROR: new row violates row-level security policy for table "rls_test_permissiv SET ROLE regress_s1; -- With both internal and hook policies, restrictive EXPLAIN (costs off) SELECT * FROM rls_test_restrictive; - QUERY PLAN ---------------------------------------------------------------- - Subquery Scan on rls_test_restrictive - Filter: ((rls_test_restrictive.data % 2) = 0) - -> Seq Scan on rls_test_restrictive rls_test_restrictive_1 - Filter: ("current_user"() = supervisor) -(4 rows) + QUERY PLAN +------------------------------------------------------------------ + Seq Scan on rls_test_restrictive + Filter: (("current_user"() = supervisor) AND ((data % 2) = 0)) +(2 rows) SELECT * FROM rls_test_restrictive; username | supervisor | data @@ -173,13 +171,11 @@ ERROR: new row violates row-level security policy for table "rls_test_restricti -- With both internal and hook policies, both permissive -- and restrictive hook policies EXPLAIN (costs off) SELECT * FROM rls_test_both; - QUERY PLAN -------------------------------------------------------------------------------------------- - Subquery Scan on rls_test_both - Filter: (((rls_test_both.data % 2) = 0) OR ("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: (("current_user"() = supervisor) AND (((data % 2) = 0) OR ("current_user"() = username))) +(2 rows) SELECT * FROM rls_test_both; username | supervisor | data diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out index 0391b8eec19..564218b767a 100644 --- a/src/test/regress/expected/equivclass.out +++ b/src/test/regress/expected/equivclass.out @@ -381,3 +381,45 @@ explain (costs off) Index Cond: (ff = '42'::bigint) (14 rows) +-- check effects of row-level security +set enable_nestloop = on; +set enable_mergejoin = off; +alter table ec1 enable row level security; +create policy p1 on ec1 using (f1 < '5'::int8alias1); +create user regress_user_ectest; +grant select on ec0 to regress_user_ectest; +grant select on ec1 to regress_user_ectest; +-- without any RLS, we'll treat {a.ff, b.ff, 43} as an EquivalenceClass +explain (costs off) + select * from ec0 a, ec1 b + where a.ff = b.ff and a.ff = 43::bigint::int8alias1; + QUERY PLAN +--------------------------------------------- + Nested Loop + -> Index Scan using ec0_pkey on ec0 a + Index Cond: (ff = '43'::int8alias1) + -> Index Scan using ec1_pkey on ec1 b + Index Cond: (ff = '43'::int8alias1) +(5 rows) + +set session authorization regress_user_ectest; +-- with RLS active, the non-leakproof a.ff = 43 clause is not treated +-- as a suitable source for an EquivalenceClass; currently, this is true +-- even though the RLS clause has nothing to do directly with the EC +explain (costs off) + select * from ec0 a, ec1 b + where a.ff = b.ff and a.ff = 43::bigint::int8alias1; + QUERY PLAN +--------------------------------------------- + Nested Loop + -> Index Scan using ec0_pkey on ec0 a + Index Cond: (ff = '43'::int8alias1) + -> Index Scan using ec1_pkey on ec1 b + Index Cond: (ff = a.ff) + Filter: (f1 < '5'::int8alias1) +(6 rows) + +reset session authorization; +revoke select on ec0 from regress_user_ectest; +revoke select on ec1 from regress_user_ectest; +drop user regress_user_ectest; diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index 471e405c7ab..25407bf9ddd 100644 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -265,32 +265,28 @@ NOTICE: f_leak => awesome science fiction (5 rows) EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); - QUERY PLAN ----------------------------------------------------------- - Subquery Scan on document - Filter: f_leak(document.dtitle) - -> Seq Scan on document document_1 - Filter: (dlevel <= $0) - InitPlan 1 (returns $0) - -> Index Scan using uaccount_pkey on uaccount - Index Cond: (pguser = CURRENT_USER) -(7 rows) + QUERY PLAN +---------------------------------------------------- + Seq Scan on document + Filter: ((dlevel <= $0) AND f_leak(dtitle)) + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) +(5 rows) EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); - QUERY PLAN ----------------------------------------------------------------------- + QUERY PLAN +----------------------------------------------------------- Hash Join Hash Cond: (category.cid = document.cid) + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) -> Seq Scan on category -> Hash - -> Subquery Scan on document - Filter: f_leak(document.dtitle) - -> Seq Scan on document document_1 - Filter: (dlevel <= $0) - InitPlan 1 (returns $0) - -> Index Scan using uaccount_pkey on uaccount - Index Cond: (pguser = CURRENT_USER) -(11 rows) + -> Seq Scan on document + Filter: ((dlevel <= $0) AND f_leak(dtitle)) +(9 rows) -- viewpoint from regress_rls_dave SET SESSION AUTHORIZATION regress_rls_dave; @@ -333,32 +329,28 @@ NOTICE: f_leak => awesome technology book (7 rows) EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); - QUERY PLAN ---------------------------------------------------------------------------------- - Subquery Scan on document - Filter: f_leak(document.dtitle) - -> Seq Scan on document document_1 - Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0)) - InitPlan 1 (returns $0) - -> Index Scan using uaccount_pkey on uaccount - Index Cond: (pguser = CURRENT_USER) -(7 rows) + QUERY PLAN +---------------------------------------------------------------------------------------------- + Seq Scan on document + Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle)) + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) +(5 rows) EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); - QUERY PLAN ---------------------------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------------------------------- Hash Join Hash Cond: (category.cid = document.cid) + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) -> Seq Scan on category -> Hash - -> Subquery Scan on document - Filter: f_leak(document.dtitle) - -> Seq Scan on document document_1 - Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0)) - InitPlan 1 (returns $0) - -> Index Scan using uaccount_pkey on uaccount - Index Cond: (pguser = CURRENT_USER) -(11 rows) + -> Seq Scan on document + Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle)) +(9 rows) -- 44 would technically fail for both p2r and p1r, but we should get an error -- back from p1r for this because it sorts first @@ -431,25 +423,21 @@ NOTICE: f_leak => great manga (3 rows) EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); - QUERY PLAN ------------------------------------------- - Subquery Scan on document - Filter: f_leak(document.dtitle) - -> Seq Scan on document document_1 - Filter: (dauthor = CURRENT_USER) -(4 rows) + QUERY PLAN +--------------------------------------------------------- + Seq Scan on document + Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) +(2 rows) EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); - QUERY PLAN --------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------- Nested Loop - -> Subquery Scan on document - Filter: f_leak(document.dtitle) - -> Seq Scan on document document_1 - Filter: (dauthor = CURRENT_USER) + -> Seq Scan on document + Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) -> Index Scan using category_pkey on category Index Cond: (cid = document.cid) -(7 rows) +(5 rows) -- interaction of FK/PK constraints SET SESSION AUTHORIZATION regress_rls_alice; @@ -698,18 +686,16 @@ NOTICE: f_leak => yyy (5 rows) EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); - QUERY PLAN -------------------------------------- - Subquery Scan on t1 - Filter: f_leak(t1.b) - -> Append - -> Seq Scan on t1 t1_1 - Filter: ((a % 2) = 0) - -> Seq Scan on t2 - Filter: ((a % 2) = 0) - -> Seq Scan on t3 - Filter: ((a % 2) = 0) -(9 rows) + QUERY PLAN +----------------------------------------------- + Append + -> Seq Scan on t1 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t3 + Filter: (((a % 2) = 0) AND f_leak(b)) +(7 rows) -- reference to system column SELECT oid, * FROM t1; @@ -769,20 +755,17 @@ SELECT * FROM t1 FOR SHARE; (5 rows) EXPLAIN (COSTS OFF) SELECT * FROM t1 FOR SHARE; - QUERY PLAN -------------------------------------------------------- + QUERY PLAN +------------------------------------- LockRows - -> Subquery Scan on t1 - -> LockRows - -> Result - -> Append - -> Seq Scan on t1 t1_1 - Filter: ((a % 2) = 0) - -> Seq Scan on t2 - Filter: ((a % 2) = 0) - -> Seq Scan on t3 - Filter: ((a % 2) = 0) -(11 rows) + -> Append + -> Seq Scan on t1 + Filter: ((a % 2) = 0) + -> Seq Scan on t2 + Filter: ((a % 2) = 0) + -> Seq Scan on t3 + Filter: ((a % 2) = 0) +(8 rows) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE; NOTICE: f_leak => bbb @@ -800,21 +783,17 @@ NOTICE: f_leak => yyy (5 rows) EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE; - QUERY PLAN -------------------------------------------------------- + QUERY PLAN +----------------------------------------------------- LockRows - -> Subquery Scan on t1 - Filter: f_leak(t1.b) - -> LockRows - -> Result - -> Append - -> Seq Scan on t1 t1_1 - Filter: ((a % 2) = 0) - -> Seq Scan on t2 - Filter: ((a % 2) = 0) - -> Seq Scan on t3 - Filter: ((a % 2) = 0) -(12 rows) + -> Append + -> Seq Scan on t1 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t3 + Filter: (((a % 2) = 0) AND f_leak(b)) +(8 rows) -- union all query SELECT a, b, oid FROM t2 UNION ALL SELECT a, b, oid FROM t3; @@ -1024,21 +1003,14 @@ NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c (2 rows) EXPLAIN (COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b); - QUERY PLAN ----------------------------------------------------------- - Subquery Scan on s1 - Filter: f_leak(s1.b) - -> Hash Join - Hash Cond: (s1_1.a = s2.x) - -> Seq Scan on s1 s1_1 - -> Hash - -> HashAggregate - Group Key: s2.x - -> Subquery Scan on s2 - Filter: (s2.y ~~ '%2f%'::text) - -> Seq Scan on s2 s2_1 - Filter: ((x % 2) = 0) -(12 rows) + QUERY PLAN +----------------------------------------------------------- + Seq Scan on s1 + Filter: ((hashed SubPlan 1) AND f_leak(b)) + SubPlan 1 + -> Seq Scan on s2 + Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text)) +(5 rows) SET SESSION AUTHORIZATION regress_rls_alice; ALTER POLICY p1 ON s1 USING (a in (select x from v2)); -- using VIEW in RLS policy @@ -1053,21 +1025,14 @@ NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc (2 rows) EXPLAIN (COSTS OFF) SELECT * FROM s1 WHERE f_leak(b); - QUERY PLAN ----------------------------------------------------------- - Subquery Scan on s1 - Filter: f_leak(s1.b) - -> Hash Join - Hash Cond: (s1_1.a = s2.x) - -> Seq Scan on s1 s1_1 - -> Hash - -> HashAggregate - Group Key: s2.x - -> Subquery Scan on s2 - Filter: (s2.y ~~ '%af%'::text) - -> Seq Scan on s2 s2_1 - Filter: ((x % 2) = 0) -(12 rows) + QUERY PLAN +----------------------------------------------------------- + Seq Scan on s1 + Filter: ((hashed SubPlan 1) AND f_leak(b)) + SubPlan 1 + -> Seq Scan on s2 + Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text)) +(5 rows) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%'; xx | x | y @@ -1078,24 +1043,18 @@ SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%'; (3 rows) EXPLAIN (COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%'; - QUERY PLAN --------------------------------------------------------------------- - Subquery Scan on s2 - Filter: (s2.y ~~ '%28%'::text) - -> Seq Scan on s2 s2_1 - Filter: ((x % 2) = 0) - SubPlan 1 + QUERY PLAN +------------------------------------------------------------------------- + Seq Scan on s2 + Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text)) + SubPlan 2 -> Limit - -> Subquery Scan on s1 - -> Nested Loop Semi Join - Join Filter: (s1_1.a = s2_2.x) - -> Seq Scan on s1 s1_1 - -> Materialize - -> Subquery Scan on s2_2 - Filter: (s2_2.y ~~ '%af%'::text) - -> Seq Scan on s2 s2_3 - Filter: ((x % 2) = 0) -(15 rows) + -> Seq Scan on s1 + Filter: (hashed SubPlan 1) + SubPlan 1 + -> Seq Scan on s2 s2_1 + Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text)) +(9 rows) SET SESSION AUTHORIZATION regress_rls_alice; ALTER POLICY p2 ON s2 USING (x in (select a from s1 where b like '%d2%')); @@ -1239,28 +1198,19 @@ EXPLAIN (COSTS OFF) EXECUTE p2(2); -- SET SESSION AUTHORIZATION regress_rls_bob; EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b); - QUERY PLAN -------------------------------------------- - Update on t1 t1_3 - Update on t1 t1_3 - Update on t2 t1 - Update on t3 t1 - -> Subquery Scan on t1 - Filter: f_leak(t1.b) - -> LockRows - -> Seq Scan on t1 t1_4 - Filter: ((a % 2) = 0) - -> Subquery Scan on t1_1 - Filter: f_leak(t1_1.b) - -> LockRows - -> Seq Scan on t2 - Filter: ((a % 2) = 0) - -> Subquery Scan on t1_2 - Filter: f_leak(t1_2.b) - -> LockRows - -> Seq Scan on t3 - Filter: ((a % 2) = 0) -(19 rows) + QUERY PLAN +----------------------------------------------- + Update on t1 + Update on t1 + Update on t2 + Update on t3 + -> Seq Scan on t1 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t3 + Filter: (((a % 2) = 0) AND f_leak(b)) +(10 rows) UPDATE t1 SET b = b || b WHERE f_leak(b); NOTICE: f_leak => bbb @@ -1269,15 +1219,12 @@ NOTICE: f_leak => bcd NOTICE: f_leak => def NOTICE: f_leak => yyy EXPLAIN (COSTS OFF) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b); - QUERY PLAN -------------------------------------------- - Update on t1 t1_1 - -> Subquery Scan on t1 - Filter: f_leak(t1.b) - -> LockRows - -> Seq Scan on t1 t1_2 - Filter: ((a % 2) = 0) -(6 rows) + QUERY PLAN +----------------------------------------------- + Update on t1 + -> Seq Scan on t1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(3 rows) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b); NOTICE: f_leak => bbbbbb @@ -1325,89 +1272,63 @@ NOTICE: f_leak => yyyyyy -- updates with from clause EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t3 WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b); - QUERY PLAN ---------------------------------------------------------------- - Update on t2 t2_1 + QUERY PLAN +----------------------------------------------------------------- + Update on t2 -> Nested Loop - -> Subquery Scan on t2 - Filter: f_leak(t2.b) - -> LockRows - -> Seq Scan on t2 t2_2 - Filter: ((a = 3) AND ((a % 2) = 1)) + -> Seq Scan on t2 + Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) -> Seq Scan on t3 - Filter: (f_leak(b) AND (a = 2)) -(9 rows) + Filter: ((a = 2) AND f_leak(b)) +(6 rows) UPDATE t2 SET b=t2.b FROM t3 WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b); NOTICE: f_leak => cde -NOTICE: f_leak => xxx -NOTICE: f_leak => zzz NOTICE: f_leak => yyyyyy EXPLAIN (COSTS OFF) UPDATE t1 SET b=t1.b FROM t2 WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); - QUERY PLAN ---------------------------------------------------------------- - Update on t1 t1_3 - Update on t1 t1_3 - Update on t2 t1 - Update on t3 t1 + QUERY PLAN +----------------------------------------------------------------- + Update on t1 + Update on t1 + Update on t2 t2_1 + Update on t3 -> Nested Loop - -> Subquery Scan on t1 - Filter: f_leak(t1.b) - -> LockRows - -> Seq Scan on t1 t1_4 - Filter: ((a = 3) AND ((a % 2) = 0)) - -> Subquery Scan on t2 - Filter: f_leak(t2.b) - -> Seq Scan on t2 t2_3 - Filter: ((a = 3) AND ((a % 2) = 1)) + -> Seq Scan on t1 + Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 + Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) -> Nested Loop - -> Subquery Scan on t1_1 - Filter: f_leak(t1_1.b) - -> LockRows - -> Seq Scan on t2 t2_4 - Filter: ((a = 3) AND ((a % 2) = 0)) - -> Subquery Scan on t2_1 - Filter: f_leak(t2_1.b) - -> Seq Scan on t2 t2_5 - Filter: ((a = 3) AND ((a % 2) = 1)) + -> Seq Scan on t2 t2_1 + Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 + Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) -> Nested Loop - -> Subquery Scan on t1_2 - Filter: f_leak(t1_2.b) - -> LockRows - -> Seq Scan on t3 - Filter: ((a = 3) AND ((a % 2) = 0)) - -> Subquery Scan on t2_2 - Filter: f_leak(t2_2.b) - -> Seq Scan on t2 t2_6 - Filter: ((a = 3) AND ((a % 2) = 1)) -(34 rows) + -> Seq Scan on t3 + Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 + Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) +(19 rows) UPDATE t1 SET b=t1.b FROM t2 WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1 WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); - QUERY PLAN ---------------------------------------------------------------------- - Update on t2 t2_1 + QUERY PLAN +----------------------------------------------------------------------- + Update on t2 -> Nested Loop - -> Subquery Scan on t2 - Filter: f_leak(t2.b) - -> LockRows - -> Seq Scan on t2 t2_2 - Filter: ((a = 3) AND ((a % 2) = 1)) - -> Subquery Scan on t1 - Filter: f_leak(t1.b) - -> Result - -> Append - -> Seq Scan on t1 t1_1 - Filter: ((a = 3) AND ((a % 2) = 0)) - -> Seq Scan on t2 t2_3 - Filter: ((a = 3) AND ((a % 2) = 0)) - -> Seq Scan on t3 - Filter: ((a = 3) AND ((a % 2) = 0)) -(17 rows) + -> Seq Scan on t2 + Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) + -> Append + -> Seq Scan on t1 + Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 t2_1 + Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t3 + Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) +(11 rows) UPDATE t2 SET b=t2.b FROM t1 WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); @@ -1416,21 +1337,16 @@ NOTICE: f_leak => cde EXPLAIN (COSTS OFF) UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2 WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2; - QUERY PLAN ---------------------------------------------------------------- - Update on t2 t2_1_1 + QUERY PLAN +----------------------------------------------------------------- + Update on t2 t2_1 -> Nested Loop Join Filter: (t2_1.b = t2_2.b) - -> Subquery Scan on t2_1 - Filter: f_leak(t2_1.b) - -> LockRows - -> Seq Scan on t2 t2_1_2 - Filter: ((a = 3) AND ((a % 2) = 1)) - -> Subquery Scan on t2_2 - Filter: f_leak(t2_2.b) - -> Seq Scan on t2 t2_2_1 - Filter: ((a = 3) AND ((a % 2) = 1)) -(12 rows) + -> Seq Scan on t2 t2_1 + Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) + -> Seq Scan on t2 t2_2 + Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) +(7 rows) UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2 WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b @@ -1445,61 +1361,46 @@ NOTICE: f_leak => cde EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2 WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; - QUERY PLAN ---------------------------------------------------------------- - Update on t1 t1_1_3 - Update on t1 t1_1_3 - Update on t2 t1_1 - Update on t3 t1_1 + QUERY PLAN +----------------------------------------------------------------------- + Update on t1 t1_1 + Update on t1 t1_1 + Update on t2 t1_1_1 + Update on t3 t1_1_2 -> Nested Loop Join Filter: (t1_1.b = t1_2.b) - -> Subquery Scan on t1_1 - Filter: f_leak(t1_1.b) - -> LockRows - -> Seq Scan on t1 t1_1_4 - Filter: ((a = 4) AND ((a % 2) = 0)) - -> Subquery Scan on t1_2 - Filter: f_leak(t1_2.b) - -> Append - -> Seq Scan on t1 t1_2_3 - Filter: ((a = 4) AND ((a % 2) = 0)) - -> Seq Scan on t2 t1_2_4 - Filter: ((a = 4) AND ((a % 2) = 0)) - -> Seq Scan on t3 t1_2_5 - Filter: ((a = 4) AND ((a % 2) = 0)) + -> Seq Scan on t1 t1_1 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Append + -> Seq Scan on t1 t1_2 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 t1_2_1 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t3 t1_2_2 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Nested Loop - Join Filter: (t1_1_1.b = t1_2_1.b) - -> Subquery Scan on t1_1_1 - Filter: f_leak(t1_1_1.b) - -> LockRows - -> Seq Scan on t2 t1_1_5 - Filter: ((a = 4) AND ((a % 2) = 0)) - -> Subquery Scan on t1_2_1 - Filter: f_leak(t1_2_1.b) - -> Append - -> Seq Scan on t1 t1_2_6 - Filter: ((a = 4) AND ((a % 2) = 0)) - -> Seq Scan on t2 t1_2_7 - Filter: ((a = 4) AND ((a % 2) = 0)) - -> Seq Scan on t3 t1_2_8 - Filter: ((a = 4) AND ((a % 2) = 0)) + Join Filter: (t1_1_1.b = t1_2.b) + -> Seq Scan on t2 t1_1_1 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Append + -> Seq Scan on t1 t1_2 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 t1_2_1 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t3 t1_2_2 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Nested Loop - Join Filter: (t1_1_2.b = t1_2_2.b) - -> Subquery Scan on t1_1_2 - Filter: f_leak(t1_1_2.b) - -> LockRows - -> Seq Scan on t3 t1_1_6 - Filter: ((a = 4) AND ((a % 2) = 0)) - -> Subquery Scan on t1_2_2 - Filter: f_leak(t1_2_2.b) - -> Append - -> Seq Scan on t1 t1_2_9 - Filter: ((a = 4) AND ((a % 2) = 0)) - -> Seq Scan on t2 t1_2_10 - Filter: ((a = 4) AND ((a % 2) = 0)) - -> Seq Scan on t3 t1_2_11 - Filter: ((a = 4) AND ((a % 2) = 0)) -(52 rows) + Join Filter: (t1_1_2.b = t1_2.b) + -> Seq Scan on t3 t1_1_2 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Append + -> Seq Scan on t1 t1_2 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 t1_2_1 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t3 t1_2_2 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) +(37 rows) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2 WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b @@ -1537,39 +1438,27 @@ SELECT * FROM t1 ORDER BY a,b; SET SESSION AUTHORIZATION regress_rls_bob; SET row_security TO ON; EXPLAIN (COSTS OFF) DELETE FROM only t1 WHERE f_leak(b); - QUERY PLAN -------------------------------------------- - Delete on t1 t1_1 - -> Subquery Scan on t1 - Filter: f_leak(t1.b) - -> LockRows - -> Seq Scan on t1 t1_2 - Filter: ((a % 2) = 0) -(6 rows) + QUERY PLAN +----------------------------------------------- + Delete on t1 + -> Seq Scan on t1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(3 rows) EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b); - QUERY PLAN -------------------------------------------- - Delete on t1 t1_3 - Delete on t1 t1_3 - Delete on t2 t1 - Delete on t3 t1 - -> Subquery Scan on t1 - Filter: f_leak(t1.b) - -> LockRows - -> Seq Scan on t1 t1_4 - Filter: ((a % 2) = 0) - -> Subquery Scan on t1_1 - Filter: f_leak(t1_1.b) - -> LockRows - -> Seq Scan on t2 - Filter: ((a % 2) = 0) - -> Subquery Scan on t1_2 - Filter: f_leak(t1_2.b) - -> LockRows - -> Seq Scan on t3 - Filter: ((a % 2) = 0) -(19 rows) + QUERY PLAN +----------------------------------------------- + Delete on t1 + Delete on t1 + Delete on t2 + Delete on t3 + -> Seq Scan on t1 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t3 + Filter: (((a % 2) = 0) AND f_leak(b)) +(10 rows) DELETE FROM only t1 WHERE f_leak(b) RETURNING oid, *, t1; NOTICE: f_leak => bbbbbb_updt @@ -1634,30 +1523,22 @@ INSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check ERROR: new row violates row-level security policy for table "b1" INSERT INTO bv1 VALUES (12, 'xxx'); -- ok EXPLAIN (COSTS OFF) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b); - QUERY PLAN ---------------------------------------------------------------------------- - Update on b1 b1_1 - -> Subquery Scan on b1 - Filter: f_leak(b1.b) - -> Subquery Scan on b1_2 - -> LockRows - -> Seq Scan on b1 b1_3 - Filter: ((a > 0) AND (a = 4) AND ((a % 2) = 0)) -(7 rows) + QUERY PLAN +----------------------------------------------------------------------- + Update on b1 + -> Seq Scan on b1 + Filter: ((a > 0) AND (a = 4) AND ((a % 2) = 0) AND f_leak(b)) +(3 rows) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b); NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c EXPLAIN (COSTS OFF) DELETE FROM bv1 WHERE a = 6 AND f_leak(b); - QUERY PLAN ---------------------------------------------------------------------------- - Delete on b1 b1_1 - -> Subquery Scan on b1 - Filter: f_leak(b1.b) - -> Subquery Scan on b1_2 - -> LockRows - -> Seq Scan on b1 b1_3 - Filter: ((a > 0) AND (a = 6) AND ((a % 2) = 0)) -(7 rows) + QUERY PLAN +----------------------------------------------------------------------- + Delete on b1 + -> Seq Scan on b1 + Filter: ((a > 0) AND (a = 6) AND ((a % 2) = 0) AND f_leak(b)) +(3 rows) DELETE FROM bv1 WHERE a = 6 AND f_leak(b); NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc @@ -1848,53 +1729,45 @@ NOTICE: f_leak => dad (2 rows) EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); - QUERY PLAN -------------------------------- - Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 0) -(4 rows) + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(2 rows) PREPARE plancache_test AS SELECT * FROM z1 WHERE f_leak(b); EXPLAIN (COSTS OFF) EXECUTE plancache_test; - QUERY PLAN -------------------------------- - Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 0) -(4 rows) + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(2 rows) PREPARE plancache_test2 AS WITH q AS (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2; EXPLAIN (COSTS OFF) EXECUTE plancache_test2; - QUERY PLAN ---------------------------------------- + QUERY PLAN +------------------------------------------------- Nested Loop CTE q - -> Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 0) + -> Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) -> CTE Scan on q -> Materialize -> Seq Scan on z2 -(9 rows) +(7 rows) PREPARE plancache_test3 AS WITH q AS (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b); EXPLAIN (COSTS OFF) EXECUTE plancache_test3; - QUERY PLAN -------------------------------------------- + QUERY PLAN +----------------------------------------------------- Nested Loop CTE q -> Seq Scan on z2 -> CTE Scan on q -> Materialize - -> Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 0) -(9 rows) + -> Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(7 rows) SET ROLE regress_rls_group1; SELECT * FROM z1 WHERE f_leak(b); @@ -1907,50 +1780,42 @@ NOTICE: f_leak => dad (2 rows) EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); - QUERY PLAN -------------------------------- - Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 0) -(4 rows) + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(2 rows) EXPLAIN (COSTS OFF) EXECUTE plancache_test; - QUERY PLAN -------------------------------- - Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 0) -(4 rows) + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(2 rows) EXPLAIN (COSTS OFF) EXECUTE plancache_test2; - QUERY PLAN ---------------------------------------- + QUERY PLAN +------------------------------------------------- Nested Loop CTE q - -> Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 0) + -> Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) -> CTE Scan on q -> Materialize -> Seq Scan on z2 -(9 rows) +(7 rows) EXPLAIN (COSTS OFF) EXECUTE plancache_test3; - QUERY PLAN -------------------------------------------- + QUERY PLAN +----------------------------------------------------- Nested Loop CTE q -> Seq Scan on z2 -> CTE Scan on q -> Materialize - -> Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 0) -(9 rows) + -> Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(7 rows) SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM z1 WHERE f_leak(b); @@ -1963,50 +1828,42 @@ NOTICE: f_leak => ccc (2 rows) EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); - QUERY PLAN -------------------------------- - Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 1) -(4 rows) + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 1) AND f_leak(b)) +(2 rows) EXPLAIN (COSTS OFF) EXECUTE plancache_test; - QUERY PLAN -------------------------------- - Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 1) -(4 rows) + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 1) AND f_leak(b)) +(2 rows) EXPLAIN (COSTS OFF) EXECUTE plancache_test2; - QUERY PLAN ---------------------------------------- + QUERY PLAN +------------------------------------------------- Nested Loop CTE q - -> Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 1) + -> Seq Scan on z1 + Filter: (((a % 2) = 1) AND f_leak(b)) -> CTE Scan on q -> Materialize -> Seq Scan on z2 -(9 rows) +(7 rows) EXPLAIN (COSTS OFF) EXECUTE plancache_test3; - QUERY PLAN -------------------------------------------- + QUERY PLAN +----------------------------------------------------- Nested Loop CTE q -> Seq Scan on z2 -> CTE Scan on q -> Materialize - -> Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 1) -(9 rows) + -> Seq Scan on z1 + Filter: (((a % 2) = 1) AND f_leak(b)) +(7 rows) SET ROLE regress_rls_group2; SELECT * FROM z1 WHERE f_leak(b); @@ -2019,50 +1876,42 @@ NOTICE: f_leak => ccc (2 rows) EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); - QUERY PLAN -------------------------------- - Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 1) -(4 rows) + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 1) AND f_leak(b)) +(2 rows) EXPLAIN (COSTS OFF) EXECUTE plancache_test; - QUERY PLAN -------------------------------- - Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 1) -(4 rows) + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 1) AND f_leak(b)) +(2 rows) EXPLAIN (COSTS OFF) EXECUTE plancache_test2; - QUERY PLAN ---------------------------------------- + QUERY PLAN +------------------------------------------------- Nested Loop CTE q - -> Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 1) + -> Seq Scan on z1 + Filter: (((a % 2) = 1) AND f_leak(b)) -> CTE Scan on q -> Materialize -> Seq Scan on z2 -(9 rows) +(7 rows) EXPLAIN (COSTS OFF) EXECUTE plancache_test3; - QUERY PLAN -------------------------------------------- + QUERY PLAN +----------------------------------------------------- Nested Loop CTE q -> Seq Scan on z2 -> CTE Scan on q -> Materialize - -> Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 1) -(9 rows) + -> Seq Scan on z1 + Filter: (((a % 2) = 1) AND f_leak(b)) +(7 rows) -- -- Views should follow policy for view owner. @@ -2133,13 +1982,11 @@ NOTICE: f_leak => dad (2 rows) EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN -------------------------------- - Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 0) -(4 rows) + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(2 rows) -- Query as role that is not owner of table but is owner of view. -- Should return records based on view owner policies. @@ -2154,13 +2001,11 @@ NOTICE: f_leak => dad (2 rows) EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN -------------------------------- - Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 0) -(4 rows) + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(2 rows) -- Query as role that is not the owner of the table or view without permissions. SET SESSION AUTHORIZATION regress_rls_carol; @@ -2181,13 +2026,11 @@ NOTICE: f_leak => dad (2 rows) EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN -------------------------------- - Subquery Scan on z1 - Filter: f_leak(z1.b) - -> Seq Scan on z1 z1_1 - Filter: ((a % 2) = 0) -(4 rows) + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(2 rows) SET SESSION AUTHORIZATION regress_rls_bob; DROP VIEW rls_view; @@ -2333,13 +2176,11 @@ SET SESSION AUTHORIZATION regress_rls_bob; CREATE VIEW rls_sbv WITH (security_barrier) AS SELECT * FROM y1 WHERE f_leak(b); EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1); - QUERY PLAN ----------------------------------------------------------- - Subquery Scan on y1 - Filter: f_leak(y1.b) - -> Seq Scan on y1 y1_1 - Filter: ((a = 1) AND ((a > 2) OR ((a % 2) = 0))) -(4 rows) + QUERY PLAN +------------------------------------------------------------------ + Seq Scan on y1 + Filter: ((a = 1) AND ((a > 2) OR ((a % 2) = 0)) AND f_leak(b)) +(2 rows) DROP VIEW rls_sbv; -- @@ -2384,13 +2225,11 @@ NOTICE: f_leak => 98f13708210194c475687be6106a3b84 (14 rows) EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak(b); - QUERY PLAN -------------------------------------------------------------------- - 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)) -(4 rows) + QUERY PLAN +----------------------------------------------------------------------------- + Seq Scan on y2 + Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b)) +(2 rows) -- -- Qual push-down of leaky functions, when not referring to table @@ -2485,17 +2324,15 @@ NOTICE: f_leak => 98f13708210194c475687be6106a3b84 (0 rows) EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b); - QUERY PLAN -------------------------------------------------------------------------------- + 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) + -> Seq Scan on y2 + Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b)) +(6 rows) DROP TABLE test_qual_pushdown; -- @@ -2581,15 +2418,13 @@ NOTICE: f_leak => 98f13708210194c475687be6106a3b84 (11 rows) EXPLAIN (COSTS OFF) WITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1; - QUERY PLAN ---------------------------------------- + QUERY PLAN +------------------------------------------------- CTE Scan on cte1 CTE cte1 - -> Subquery Scan on t1 - Filter: f_leak(t1.b) - -> Seq Scan on t1 t1_1 - Filter: ((a % 2) = 0) -(6 rows) + -> Seq Scan on t1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(4 rows) WITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail ERROR: new row violates row-level security policy for table "t1" @@ -3100,17 +2935,13 @@ SELECT * FROM current_check; -- Plan should be a subquery TID scan EXPLAIN (COSTS OFF) UPDATE current_check SET payload = payload WHERE CURRENT OF current_check_cursor; - QUERY PLAN ---------------------------------------------------------------------- - Update on current_check current_check_1 - -> Subquery Scan on current_check - -> Subquery Scan on current_check_2 - Filter: ((current_check_2.currentid % 2) = 0) - -> LockRows - -> Tid Scan on current_check current_check_3 - TID Cond: CURRENT OF current_check_cursor - Filter: (currentid = 4) -(8 rows) + QUERY PLAN +------------------------------------------------------------- + Update on current_check + -> Tid Scan on current_check + TID Cond: CURRENT OF current_check_cursor + Filter: ((currentid = 4) AND ((currentid % 2) = 0)) +(4 rows) -- Similarly can only delete row 4 FETCH ABSOLUTE 1 FROM current_check_cursor; diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index 6fba613f0f5..2da3c069e11 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -1896,26 +1896,20 @@ EXPLAIN (costs off) SELECT * FROM rw_view1 WHERE snoop(person); (4 rows) EXPLAIN (costs off) UPDATE rw_view1 SET person=person WHERE snoop(person); - QUERY PLAN ------------------------------------------------------------ - Update on base_tbl base_tbl_1 - -> Subquery Scan on base_tbl - Filter: snoop(base_tbl.person) - -> LockRows - -> Seq Scan on base_tbl base_tbl_2 - Filter: (visibility = 'public'::text) -(6 rows) + QUERY PLAN +------------------------------------------------------------------- + Update on base_tbl + -> Seq Scan on base_tbl + Filter: ((visibility = 'public'::text) AND snoop(person)) +(3 rows) EXPLAIN (costs off) DELETE FROM rw_view1 WHERE NOT snoop(person); - QUERY PLAN ------------------------------------------------------------ - Delete on base_tbl base_tbl_1 - -> Subquery Scan on base_tbl - Filter: (NOT snoop(base_tbl.person)) - -> LockRows - -> Seq Scan on base_tbl base_tbl_2 - Filter: (visibility = 'public'::text) -(6 rows) + QUERY PLAN +------------------------------------------------------------------------- + Delete on base_tbl + -> Seq Scan on base_tbl + Filter: ((visibility = 'public'::text) AND (NOT snoop(person))) +(3 rows) -- security barrier view on top of security barrier view CREATE VIEW rw_view2 WITH (security_barrier = true) AS @@ -1978,30 +1972,20 @@ EXPLAIN (costs off) SELECT * FROM rw_view2 WHERE snoop(person); (6 rows) EXPLAIN (costs off) UPDATE rw_view2 SET person=person WHERE snoop(person); - QUERY PLAN ------------------------------------------------------------------ - Update on base_tbl base_tbl_1 - -> Subquery Scan on base_tbl - Filter: snoop(base_tbl.person) - -> Subquery Scan on base_tbl_2 - Filter: snoop(base_tbl_2.person) - -> LockRows - -> Seq Scan on base_tbl base_tbl_3 - Filter: (visibility = 'public'::text) -(8 rows) + QUERY PLAN +------------------------------------------------------------------------------------- + Update on base_tbl + -> Seq Scan on base_tbl + Filter: ((visibility = 'public'::text) AND snoop(person) AND snoop(person)) +(3 rows) EXPLAIN (costs off) DELETE FROM rw_view2 WHERE NOT snoop(person); - QUERY PLAN ------------------------------------------------------------------ - Delete on base_tbl base_tbl_1 - -> Subquery Scan on base_tbl - Filter: (NOT snoop(base_tbl.person)) - -> Subquery Scan on base_tbl_2 - Filter: snoop(base_tbl_2.person) - -> LockRows - -> Seq Scan on base_tbl base_tbl_3 - Filter: (visibility = 'public'::text) -(8 rows) + QUERY PLAN +------------------------------------------------------------------------------------------- + Delete on base_tbl + -> Seq Scan on base_tbl + Filter: ((visibility = 'public'::text) AND snoop(person) AND (NOT snoop(person))) +(3 rows) DROP TABLE base_tbl CASCADE; NOTICE: drop cascades to 2 other objects @@ -2026,18 +2010,16 @@ SELECT * FROM rw_view1; (1 row) EXPLAIN (costs off) DELETE FROM rw_view1 WHERE id = 1 AND snoop(data); - QUERY PLAN -------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------- Update on base_tbl base_tbl_1 -> Nested Loop -> Index Scan using base_tbl_pkey on base_tbl base_tbl_1 Index Cond: (id = 1) - -> Subquery Scan on base_tbl - Filter: snoop(base_tbl.data) - -> Index Scan using base_tbl_pkey on base_tbl base_tbl_2 - Index Cond: (id = 1) - Filter: (NOT deleted) -(9 rows) + -> Index Scan using base_tbl_pkey on base_tbl + Index Cond: (id = 1) + Filter: ((NOT deleted) AND snoop(data)) +(7 rows) DELETE FROM rw_view1 WHERE id = 1 AND snoop(data); NOTICE: snooped value: Row 1 @@ -2114,85 +2096,45 @@ SELECT * FROM v1 WHERE a=8; (4 rows) EXPLAIN (VERBOSE, COSTS OFF) -UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a = 3; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------- - Update on public.t1 t1_4 - Update on public.t1 t1_4 - Update on public.t11 t1 - Update on public.t12 t1 - Update on public.t111 t1 - -> Subquery Scan on t1 +UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------- + Update on public.t1 + Update on public.t1 + Update on public.t11 + Update on public.t12 + Update on public.t111 + -> Index Scan using t1_a_idx on public.t1 Output: 100, t1.b, t1.c, t1.ctid - Filter: snoop(t1.a) - -> LockRows - Output: t1_5.ctid, t1_5.a, t1_5.b, t1_5.c, t1_5.ctid, t12.ctid, t12.tableoid - -> Nested Loop Semi Join - Output: t1_5.ctid, t1_5.a, t1_5.b, t1_5.c, t1_5.ctid, t12.ctid, t12.tableoid - -> Seq Scan on public.t1 t1_5 - Output: t1_5.ctid, t1_5.a, t1_5.b, t1_5.c - Filter: ((t1_5.a > 5) AND (t1_5.a = 3) AND leakproof(t1_5.a)) - -> Append - -> Seq Scan on public.t12 - Output: t12.ctid, t12.tableoid, t12.a - Filter: (t12.a = 3) - -> Seq Scan on public.t111 - Output: t111.ctid, t111.tableoid, t111.a - Filter: (t111.a = 3) - -> Subquery Scan on t1_1 - Output: 100, t1_1.b, t1_1.c, t1_1.d, t1_1.ctid - Filter: snoop(t1_1.a) - -> LockRows - Output: t11.ctid, t11.a, t11.b, t11.c, t11.d, t11.ctid, t12_1.ctid, t12_1.tableoid - -> Nested Loop Semi Join - Output: t11.ctid, t11.a, t11.b, t11.c, t11.d, t11.ctid, t12_1.ctid, t12_1.tableoid - -> Seq Scan on public.t11 - Output: t11.ctid, t11.a, t11.b, t11.c, t11.d - Filter: ((t11.a > 5) AND (t11.a = 3) AND leakproof(t11.a)) - -> Append - -> Seq Scan on public.t12 t12_1 - Output: t12_1.ctid, t12_1.tableoid, t12_1.a - Filter: (t12_1.a = 3) - -> Seq Scan on public.t111 t111_1 - Output: t111_1.ctid, t111_1.tableoid, t111_1.a - Filter: (t111_1.a = 3) - -> Subquery Scan on t1_2 - Output: 100, t1_2.b, t1_2.c, t1_2.e, t1_2.ctid - Filter: snoop(t1_2.a) - -> LockRows - Output: t12_2.ctid, t12_2.a, t12_2.b, t12_2.c, t12_2.e, t12_2.ctid, t12_3.ctid, t12_3.tableoid - -> Nested Loop Semi Join - Output: t12_2.ctid, t12_2.a, t12_2.b, t12_2.c, t12_2.e, t12_2.ctid, t12_3.ctid, t12_3.tableoid - -> Seq Scan on public.t12 t12_2 - Output: t12_2.ctid, t12_2.a, t12_2.b, t12_2.c, t12_2.e - Filter: ((t12_2.a > 5) AND (t12_2.a = 3) AND leakproof(t12_2.a)) - -> Append - -> Seq Scan on public.t12 t12_3 - Output: t12_3.ctid, t12_3.tableoid, t12_3.a - Filter: (t12_3.a = 3) - -> Seq Scan on public.t111 t111_2 - Output: t111_2.ctid, t111_2.tableoid, t111_2.a - Filter: (t111_2.a = 3) - -> Subquery Scan on t1_3 - Output: 100, t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid - Filter: snoop(t1_3.a) - -> LockRows - Output: t111_3.ctid, t111_3.a, t111_3.b, t111_3.c, t111_3.d, t111_3.e, t111_3.ctid, t12_4.ctid, t12_4.tableoid - -> Nested Loop Semi Join - Output: t111_3.ctid, t111_3.a, t111_3.b, t111_3.c, t111_3.d, t111_3.e, t111_3.ctid, t12_4.ctid, t12_4.tableoid - -> Seq Scan on public.t111 t111_3 - Output: t111_3.ctid, t111_3.a, t111_3.b, t111_3.c, t111_3.d, t111_3.e - Filter: ((t111_3.a > 5) AND (t111_3.a = 3) AND leakproof(t111_3.a)) - -> Append - -> Seq Scan on public.t12 t12_4 - Output: t12_4.ctid, t12_4.tableoid, t12_4.a - Filter: (t12_4.a = 3) - -> Seq Scan on public.t111 t111_4 - Output: t111_4.ctid, t111_4.tableoid, t111_4.a - Filter: (t111_4.a = 3) -(73 rows) - -UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a = 3; + Index Cond: ((t1.a > 5) AND (t1.a < 7)) + Filter: ((t1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1.a) AND leakproof(t1.a)) + SubPlan 1 + -> Append + -> Seq Scan on public.t12 t12_1 + Filter: (t12_1.a = t1.a) + -> Seq Scan on public.t111 t111_1 + Filter: (t111_1.a = t1.a) + SubPlan 2 + -> Append + -> Seq Scan on public.t12 t12_2 + Output: t12_2.a + -> Seq Scan on public.t111 t111_2 + Output: t111_2.a + -> Index Scan using t11_a_idx on public.t11 + Output: 100, t11.b, t11.c, t11.d, t11.ctid + Index Cond: ((t11.a > 5) AND (t11.a < 7)) + Filter: ((t11.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t11.a) AND leakproof(t11.a)) + -> Index Scan using t12_a_idx on public.t12 + Output: 100, t12.b, t12.c, t12.e, t12.ctid + Index Cond: ((t12.a > 5) AND (t12.a < 7)) + Filter: ((t12.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t12.a) AND leakproof(t12.a)) + -> Index Scan using t111_a_idx on public.t111 + Output: 100, t111.b, t111.c, t111.d, t111.e, t111.ctid + Index Cond: ((t111.a > 5) AND (t111.a < 7)) + Filter: ((t111.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t111.a) AND leakproof(t111.a)) +(33 rows) + +UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100 a | b | c | d ---+---+---+--- @@ -2205,82 +2147,42 @@ SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100 EXPLAIN (VERBOSE, COSTS OFF) UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------- - Update on public.t1 t1_4 - Update on public.t1 t1_4 - Update on public.t11 t1 - Update on public.t12 t1 - Update on public.t111 t1 - -> Subquery Scan on t1 + QUERY PLAN +--------------------------------------------------------------------------------------------------------- + Update on public.t1 + Update on public.t1 + Update on public.t11 + Update on public.t12 + Update on public.t111 + -> Index Scan using t1_a_idx on public.t1 Output: (t1.a + 1), t1.b, t1.c, t1.ctid - Filter: snoop(t1.a) - -> LockRows - Output: t1_5.a, t1_5.ctid, t1_5.b, t1_5.c, t1_5.ctid, t12.ctid, t12.tableoid - -> Nested Loop Semi Join - Output: t1_5.a, t1_5.ctid, t1_5.b, t1_5.c, t1_5.ctid, t12.ctid, t12.tableoid - -> Seq Scan on public.t1 t1_5 - Output: t1_5.a, t1_5.ctid, t1_5.b, t1_5.c - Filter: ((t1_5.a > 5) AND (t1_5.a = 8) AND leakproof(t1_5.a)) - -> Append - -> Seq Scan on public.t12 - Output: t12.ctid, t12.tableoid, t12.a - Filter: (t12.a = 8) - -> Seq Scan on public.t111 - Output: t111.ctid, t111.tableoid, t111.a - Filter: (t111.a = 8) - -> Subquery Scan on t1_1 - Output: (t1_1.a + 1), t1_1.b, t1_1.c, t1_1.d, t1_1.ctid - Filter: snoop(t1_1.a) - -> LockRows - Output: t11.a, t11.ctid, t11.b, t11.c, t11.d, t11.ctid, t12_1.ctid, t12_1.tableoid - -> Nested Loop Semi Join - Output: t11.a, t11.ctid, t11.b, t11.c, t11.d, t11.ctid, t12_1.ctid, t12_1.tableoid - -> Seq Scan on public.t11 - Output: t11.a, t11.ctid, t11.b, t11.c, t11.d - Filter: ((t11.a > 5) AND (t11.a = 8) AND leakproof(t11.a)) - -> Append - -> Seq Scan on public.t12 t12_1 - Output: t12_1.ctid, t12_1.tableoid, t12_1.a - Filter: (t12_1.a = 8) - -> Seq Scan on public.t111 t111_1 - Output: t111_1.ctid, t111_1.tableoid, t111_1.a - Filter: (t111_1.a = 8) - -> Subquery Scan on t1_2 - Output: (t1_2.a + 1), t1_2.b, t1_2.c, t1_2.e, t1_2.ctid - Filter: snoop(t1_2.a) - -> LockRows - Output: t12_2.a, t12_2.ctid, t12_2.b, t12_2.c, t12_2.e, t12_2.ctid, t12_3.ctid, t12_3.tableoid - -> Nested Loop Semi Join - Output: t12_2.a, t12_2.ctid, t12_2.b, t12_2.c, t12_2.e, t12_2.ctid, t12_3.ctid, t12_3.tableoid - -> Seq Scan on public.t12 t12_2 - Output: t12_2.a, t12_2.ctid, t12_2.b, t12_2.c, t12_2.e - Filter: ((t12_2.a > 5) AND (t12_2.a = 8) AND leakproof(t12_2.a)) - -> Append - -> Seq Scan on public.t12 t12_3 - Output: t12_3.ctid, t12_3.tableoid, t12_3.a - Filter: (t12_3.a = 8) - -> Seq Scan on public.t111 t111_2 - Output: t111_2.ctid, t111_2.tableoid, t111_2.a - Filter: (t111_2.a = 8) - -> Subquery Scan on t1_3 - Output: (t1_3.a + 1), t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid - Filter: snoop(t1_3.a) - -> LockRows - Output: t111_3.a, t111_3.ctid, t111_3.b, t111_3.c, t111_3.d, t111_3.e, t111_3.ctid, t12_4.ctid, t12_4.tableoid - -> Nested Loop Semi Join - Output: t111_3.a, t111_3.ctid, t111_3.b, t111_3.c, t111_3.d, t111_3.e, t111_3.ctid, t12_4.ctid, t12_4.tableoid - -> Seq Scan on public.t111 t111_3 - Output: t111_3.a, t111_3.ctid, t111_3.b, t111_3.c, t111_3.d, t111_3.e - Filter: ((t111_3.a > 5) AND (t111_3.a = 8) AND leakproof(t111_3.a)) - -> Append - -> Seq Scan on public.t12 t12_4 - Output: t12_4.ctid, t12_4.tableoid, t12_4.a - Filter: (t12_4.a = 8) - -> Seq Scan on public.t111 t111_4 - Output: t111_4.ctid, t111_4.tableoid, t111_4.a - Filter: (t111_4.a = 8) -(73 rows) + Index Cond: ((t1.a > 5) AND (t1.a = 8)) + Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1.a) AND leakproof(t1.a)) + SubPlan 1 + -> Append + -> Seq Scan on public.t12 t12_1 + Filter: (t12_1.a = t1.a) + -> Seq Scan on public.t111 t111_1 + Filter: (t111_1.a = t1.a) + SubPlan 2 + -> Append + -> Seq Scan on public.t12 t12_2 + Output: t12_2.a + -> Seq Scan on public.t111 t111_2 + Output: t111_2.a + -> Index Scan using t11_a_idx on public.t11 + Output: (t11.a + 1), t11.b, t11.c, t11.d, t11.ctid + Index Cond: ((t11.a > 5) AND (t11.a = 8)) + Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t11.a) AND leakproof(t11.a)) + -> Index Scan using t12_a_idx on public.t12 + Output: (t12.a + 1), t12.b, t12.c, t12.e, t12.ctid + Index Cond: ((t12.a > 5) AND (t12.a = 8)) + Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t12.a) AND leakproof(t12.a)) + -> Index Scan using t111_a_idx on public.t111 + Output: (t111.a + 1), t111.b, t111.c, t111.d, t111.e, t111.ctid + Index Cond: ((t111.a > 5) AND (t111.a = 8)) + Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t111.a) AND leakproof(t111.a)) +(33 rows) UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; NOTICE: snooped value: 8 diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql index 17fad673e92..0e4aa0cd2c5 100644 --- a/src/test/regress/sql/equivclass.sql +++ b/src/test/regress/sql/equivclass.sql @@ -222,3 +222,35 @@ explain (costs off) union all select ff + 4 as x from ec1) as ss1 where ss1.x = ec1.f1 and ec1.ff = 42::int8; + +-- check effects of row-level security +set enable_nestloop = on; +set enable_mergejoin = off; + +alter table ec1 enable row level security; +create policy p1 on ec1 using (f1 < '5'::int8alias1); + +create user regress_user_ectest; +grant select on ec0 to regress_user_ectest; +grant select on ec1 to regress_user_ectest; + +-- without any RLS, we'll treat {a.ff, b.ff, 43} as an EquivalenceClass +explain (costs off) + select * from ec0 a, ec1 b + where a.ff = b.ff and a.ff = 43::bigint::int8alias1; + +set session authorization regress_user_ectest; + +-- with RLS active, the non-leakproof a.ff = 43 clause is not treated +-- as a suitable source for an EquivalenceClass; currently, this is true +-- even though the RLS clause has nothing to do directly with the EC +explain (costs off) + select * from ec0 a, ec1 b + where a.ff = b.ff and a.ff = 43::bigint::int8alias1; + +reset session authorization; + +revoke select on ec0 from regress_user_ectest; +revoke select on ec1 from regress_user_ectest; + +drop user regress_user_ectest; diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql index bb9a3a61746..ffc64d2de9a 100644 --- a/src/test/regress/sql/updatable_views.sql +++ b/src/test/regress/sql/updatable_views.sql @@ -1001,8 +1001,8 @@ SELECT * FROM v1 WHERE a=3; -- should not see anything SELECT * FROM v1 WHERE a=8; EXPLAIN (VERBOSE, COSTS OFF) -UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a = 3; -UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a = 3; +UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; +UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100 SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100 |