Enforce ALL/SELECT policies in RETURNING for RLS
authorStephen Frost <sfrost@snowman.net>
Tue, 15 Sep 2015 19:49:31 +0000 (15:49 -0400)
committerStephen Frost <sfrost@snowman.net>
Tue, 15 Sep 2015 19:49:31 +0000 (15:49 -0400)
For the UPDATE/DELETE RETURNING case, filter the records which are not
visible to the user through ALL or SELECT policies from those considered
for the UPDATE or DELETE.  This is similar to how the GRANT system
works, which prevents RETURNING unless the caller has SELECT rights on
the relation.

Per discussion with Robert, Dean, Tom, and Kevin.

Back-patch to 9.5 where RLS was introduced.

src/backend/rewrite/rowsecurity.c
src/test/regress/expected/rowsecurity.out

index b96c29d8f64701e7e2f32d6cd2f6ef4815160a19..c20a17802b1f0da523fdd70263c07ec00d0e0a0a 100644 (file)
@@ -186,6 +186,33 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
                           securityQuals,
                           hasSubLinks);
 
+   /*
+    * For the target relation, when there is a returning list, we need to
+    * collect up CMD_SELECT policies and add them via add_security_quals.
+    * This is because, for the RETURNING case, we have to filter any records
+    * which are not visible through an ALL or SELECT USING policy.
+    *
+    * We don't need to worry about the non-target relation case because we are
+    * checking the ALL and SELECT policies for those relations anyway (see
+    * above).
+    */
+   if (root->returningList != NIL &&
+       (commandType == CMD_UPDATE || commandType == CMD_DELETE))
+   {
+       List       *returning_permissive_policies;
+       List       *returning_restrictive_policies;
+
+       get_policies_for_relation(rel, CMD_SELECT, user_id,
+                                 &returning_permissive_policies,
+                                 &returning_restrictive_policies);
+
+       add_security_quals(rt_index,
+                          returning_permissive_policies,
+                          returning_restrictive_policies,
+                          securityQuals,
+                          hasSubLinks);
+   }
+
    /*
     * For INSERT and UPDATE, add withCheckOptions to verify that any new
     * records added are consistent with the security policies.  This will use
@@ -233,6 +260,26 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
                                   withCheckOptions,
                                   hasSubLinks);
 
+           /*
+            * Get and add ALL/SELECT policies, if there is a RETURNING clause,
+            * also as WCO policies, again, to avoid silently dropping data.
+            */
+           if (root->returningList != NIL)
+           {
+               List       *conflict_returning_permissive_policies = NIL;
+               List       *conflict_returning_restrictive_policies = NIL;
+
+               get_policies_for_relation(rel, CMD_SELECT, user_id,
+                                     &conflict_returning_permissive_policies,
+                                     &conflict_returning_restrictive_policies);
+               add_with_check_options(rel, rt_index,
+                                      WCO_RLS_CONFLICT_CHECK,
+                                      conflict_returning_permissive_policies,
+                                      conflict_returning_restrictive_policies,
+                                      withCheckOptions,
+                                      hasSubLinks);
+           }
+
            /* Enforce the WITH CHECK clauses of the UPDATE policies */
            add_with_check_options(rel, rt_index,
                                   WCO_RLS_UPDATE_CHECK,
index 6fc80af30ee438ada4b2e99c782e46826d3967ec..7628e52901a270eee86cebec0bb22ee51573d4f7 100644 (file)
@@ -1246,21 +1246,23 @@ 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                           
----------------------------------------------------------------
+                             QUERY PLAN                              
+---------------------------------------------------------------------
  Update on t2 t2_1_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_1_2
+                     Filter: ((t2_1_2.a % 2) = 1)
+                     ->  LockRows
+                           ->  Seq Scan on t2 t2_1_3
+                                 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)
+(14 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
@@ -1275,8 +1277,8 @@ 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                           
----------------------------------------------------------------
+                             QUERY PLAN                              
+---------------------------------------------------------------------
  Update on t1 t1_1_3
    Update on t1 t1_1_3
    Update on t2 t1_1
@@ -1285,9 +1287,11 @@ AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
          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_1_4
+                     Filter: ((t1_1_4.a % 2) = 0)
+                     ->  LockRows
+                           ->  Seq Scan on t1 t1_1_5
+                                 Filter: ((a = 4) AND ((a % 2) = 0))
          ->  Subquery Scan on t1_2
                Filter: f_leak(t1_2.b)
                ->  Append
@@ -1301,9 +1305,11 @@ AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
          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_1_6
+                     Filter: ((t1_1_6.a % 2) = 0)
+                     ->  LockRows
+                           ->  Seq Scan on t2 t1_1_7
+                                 Filter: ((a = 4) AND ((a % 2) = 0))
          ->  Subquery Scan on t1_2_1
                Filter: f_leak(t1_2_1.b)
                ->  Append
@@ -1317,9 +1323,11 @@ AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
          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_1_8
+                     Filter: ((t1_1_8.a % 2) = 0)
+                     ->  LockRows
+                           ->  Seq Scan on t3 t1_1_9
+                                 Filter: ((a = 4) AND ((a % 2) = 0))
          ->  Subquery Scan on t1_2_2
                Filter: f_leak(t1_2_2.b)
                ->  Append
@@ -1329,7 +1337,7 @@ AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
                            Filter: ((a = 4) AND ((a % 2) = 0))
                      ->  Seq Scan on t3 t1_2_11
                            Filter: ((a = 4) AND ((a % 2) = 0))
-(52 rows)
+(58 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
@@ -1960,8 +1968,6 @@ NOTICE:  f_leak => fgh_updt
 (6 rows)
 
 DELETE FROM x1 WHERE f_leak(b) RETURNING *;
-NOTICE:  f_leak => abc_updt
-NOTICE:  f_leak => efg_updt
 NOTICE:  f_leak => cde_updt
 NOTICE:  f_leak => fgh_updt
 NOTICE:  f_leak => bcd_updt_updt
@@ -1970,15 +1976,13 @@ NOTICE:  f_leak => fgh_updt_updt
 NOTICE:  f_leak => fgh_updt_updt
  a |       b       |         c         
 ---+---------------+-------------------
- 1 | abc_updt      | rls_regress_user1
- 5 | efg_updt      | rls_regress_user1
  3 | cde_updt      | rls_regress_user2
  7 | fgh_updt      | rls_regress_user2
  2 | bcd_updt_updt | rls_regress_user1
  4 | def_updt_updt | rls_regress_user2
  6 | fgh_updt_updt | rls_regress_user1
  8 | fgh_updt_updt | rls_regress_user2
-(8 rows)
+(6 rows)
 
 --
 -- Duplicate Policy Names