Fix usage of whole-row variables in WCO and RLS policy expressions.
authorTom Lane <[email protected]>
Thu, 12 Sep 2019 22:29:18 +0000 (18:29 -0400)
committerTom Lane <[email protected]>
Thu, 12 Sep 2019 22:29:18 +0000 (18:29 -0400)
Since WITH CHECK OPTION was introduced, ExecInitModifyTable has
initialized WCO expressions with the wrong plan node as parent -- that is,
it passed its input subplan not the ModifyTable node itself.  Up to now
we thought this was harmless, but bug #16006 from Vinay Banakar shows it's
not: if the input node is a SubqueryScan then ExecInitWholeRowVar can get
confused into doing the wrong thing.  (The fact that ExecInitWholeRowVar
contains such logic is certainly a horrid kluge that doesn't deserve to
live, but figuring out another way to do that is a task for some other day.)

Andres had already noticed the wrong-parent mistake and fixed it in commit
148e632c0, but not being aware of any user-visible consequences, he quite
reasonably didn't back-patch.  This patch is simply a back-patch of
148e632c0, plus addition of a test case based on bug #16006.  I also added
the test case to v12/HEAD, even though the bug is already fixed there.

Back-patch to all supported branches.  9.4 lacks RLS policies so the
new test case doesn't work there, but I'm pretty sure a test could be
devised based on using a whole-row Var in a plain WITH CHECK OPTION
condition.  (I lack the cycles to do so myself, though.)

Andres Freund and Tom Lane

Discussion: https://postgr.es/m/16006-99290d2e4642cbd5@postgresql.org
Discussion: https://postgr.es/m/20181205225213[email protected]

src/backend/executor/nodeModifyTable.c
src/test/regress/expected/rowsecurity.out
src/test/regress/expected/updatable_views.out
src/test/regress/sql/rowsecurity.sql

index 252324910eaa63fa1370f9cc416d24895636d4ff..031abf78d1d2d189ab77f7cb35173665f2d18509 100644 (file)
@@ -1632,7 +1632,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
        {
            WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
            ExprState  *wcoExpr = ExecInitExpr((Expr *) wco->qual,
-                                              mtstate->mt_plans[i]);
+                                              &mtstate->ps);
 
            wcoExprs = lappend(wcoExprs, wcoExpr);
        }
index 9b86c0e49eec79f6dbbda779384be8e4417d3f95..a7f72f4b9fc56a5325e6ed5152d97e62c97f04d9 100644 (file)
@@ -3513,12 +3513,47 @@ DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
 RESET SESSION AUTHORIZATION;
 DROP TABLE rls_tbl;
-DROP USER regress_rls_alice;
-DROP USER regress_rls_bob;
+-- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE TABLE rls_tbl (a int, b int, c int);
+CREATE POLICY p1 ON rls_tbl USING (rls_tbl >= ROW(1,1,1));
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_tbl FORCE ROW LEVEL SECURITY;
+INSERT INTO rls_tbl SELECT 10, 20, 30;
+EXPLAIN (VERBOSE, COSTS OFF)
+INSERT INTO rls_tbl
+  SELECT * FROM (SELECT b, c FROM rls_tbl ORDER BY a) ss;
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Insert on rls_regress_schema.rls_tbl
+   ->  Subquery Scan on ss
+         Output: ss.b, ss.c, NULL::integer
+         ->  Sort
+               Output: rls_tbl_1.b, rls_tbl_1.c, rls_tbl_1.a
+               Sort Key: rls_tbl_1.a
+               ->  Subquery Scan on rls_tbl_1
+                     Output: rls_tbl_1.b, rls_tbl_1.c, rls_tbl_1.a
+                     ->  Seq Scan on rls_regress_schema.rls_tbl rls_tbl_2
+                           Output: rls_tbl_2.b, rls_tbl_2.c, rls_tbl_2.a
+                           Filter: (rls_tbl_2.* >= ROW(1, 1, 1))
+(11 rows)
+
+INSERT INTO rls_tbl
+  SELECT * FROM (SELECT b, c FROM rls_tbl ORDER BY a) ss;
+SELECT * FROM rls_tbl;
+ a  | b  | c  
+----+----+----
+ 10 | 20 | 30
+ 20 | 30 |   
+(2 rows)
+
+DROP TABLE rls_tbl;
+RESET SESSION AUTHORIZATION;
 --
 -- Clean up objects
 --
-RESET SESSION AUTHORIZATION;
+DROP USER regress_rls_alice;
+DROP USER regress_rls_bob;
 -- Suppress NOTICE messages when doing a cascaded drop.
 SET client_min_messages TO 'warning';
 DROP SCHEMA rls_regress_schema CASCADE;
index 8c322fd06d74484809b60e4fed5e1ae55632160f..81f193002bf3c2bfec76eb3723914f77bdfc0d6c 100644 (file)
@@ -1665,31 +1665,31 @@ UPDATE rw_view1 SET a = a + 5; -- should fail
 ERROR:  new row violates check option for view "rw_view1"
 DETAIL:  Failing row contains (15).
 EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
-                          QUERY PLAN                           
----------------------------------------------------------------
+                       QUERY PLAN                        
+---------------------------------------------------------
  Insert on base_tbl b
    ->  Result
-         SubPlan 1
-           ->  Index Only Scan using ref_tbl_pkey on ref_tbl r
-                 Index Cond: (a = b.a)
-         SubPlan 2
-           ->  Seq Scan on ref_tbl r_1
+   SubPlan 1
+     ->  Index Only Scan using ref_tbl_pkey on ref_tbl r
+           Index Cond: (a = b.a)
+   SubPlan 2
+     ->  Seq Scan on ref_tbl r_1
 (7 rows)
 
 EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
-                           QUERY PLAN                            
------------------------------------------------------------------
+                        QUERY PLAN                         
+-----------------------------------------------------------
  Update on base_tbl b
    ->  Hash Semi Join
          Hash Cond: (b.a = r.a)
          ->  Seq Scan on base_tbl b
          ->  Hash
                ->  Seq Scan on ref_tbl r
-         SubPlan 1
-           ->  Index Only Scan using ref_tbl_pkey on ref_tbl r_1
-                 Index Cond: (a = b.a)
-         SubPlan 2
-           ->  Seq Scan on ref_tbl r_2
+   SubPlan 1
+     ->  Index Only Scan using ref_tbl_pkey on ref_tbl r_1
+           Index Cond: (a = b.a)
+   SubPlan 2
+     ->  Seq Scan on ref_tbl r_2
 (11 rows)
 
 DROP TABLE base_tbl, ref_tbl CASCADE;
index 303a893a2c6871e623eb592746e609bf3ee743f3..e086e85fc05f4a399f66a04ea58bae4a969e11ea 100644 (file)
@@ -1624,13 +1624,30 @@ DROP FUNCTION op_leak(int, int);
 RESET SESSION AUTHORIZATION;
 DROP TABLE rls_tbl;
 
-DROP USER regress_rls_alice;
-DROP USER regress_rls_bob;
+-- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE TABLE rls_tbl (a int, b int, c int);
+CREATE POLICY p1 ON rls_tbl USING (rls_tbl >= ROW(1,1,1));
+
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_tbl FORCE ROW LEVEL SECURITY;
+
+INSERT INTO rls_tbl SELECT 10, 20, 30;
+EXPLAIN (VERBOSE, COSTS OFF)
+INSERT INTO rls_tbl
+  SELECT * FROM (SELECT b, c FROM rls_tbl ORDER BY a) ss;
+INSERT INTO rls_tbl
+  SELECT * FROM (SELECT b, c FROM rls_tbl ORDER BY a) ss;
+SELECT * FROM rls_tbl;
+
+DROP TABLE rls_tbl;
+RESET SESSION AUTHORIZATION;
 
 --
 -- Clean up objects
 --
-RESET SESSION AUTHORIZATION;
+DROP USER regress_rls_alice;
+DROP USER regress_rls_bob;
 
 -- Suppress NOTICE messages when doing a cascaded drop.
 SET client_min_messages TO 'warning';