]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Ensure COPY TO on an RLS-enabled table copies no more than it should.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 10 Mar 2023 18:52:28 +0000 (13:52 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 10 Mar 2023 18:52:28 +0000 (13:52 -0500)
The COPY documentation is quite clear that "COPY relation TO" copies
rows from only the named table, not any inheritance children it may
have.  However, if you enabled row-level security on the table then
this stopped being true, because the code forgot to apply the ONLY
modifier in the "SELECT ... FROM relation" query that it constructs
in order to allow RLS predicates to be attached.  Fix that.

Report and patch by Antonin Houska (comment adjustments and test case
by me).  Back-patch to all supported branches.

Discussion: https://postgr.es/m/3472.1675251957@antos

src/backend/commands/copy.c
src/backend/commands/copyto.c
src/test/regress/expected/rowsecurity.out
src/test/regress/sql/rowsecurity.sql

index 2e90563333e6682c1b3a8836efcfe1a36e5d3a75..cc1909ecfc4d9982c5606ea40c8d61d2163b17c3 100644 (file)
@@ -243,11 +243,14 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
                        /*
                         * Build RangeVar for from clause, fully qualified based on the
-                        * relation which we have opened and locked.
+                        * relation which we have opened and locked.  Use "ONLY" so that
+                        * COPY retrieves rows from only the target table not any
+                        * inheritance children, the same as when RLS doesn't apply.
                         */
                        from = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
                                                                pstrdup(RelationGetRelationName(rel)),
                                                                -1);
+                       from->inh = false;      /* apply ONLY */
 
                        /* Build query */
                        select = makeNode(SelectStmt);
index fca29a9a1050009fb2835bcb6f2637a139527da1..73e286f7ea57bb9024fd83c8de963f97ac1a1149 100644 (file)
@@ -507,8 +507,8 @@ BeginCopyTo(ParseState *pstate,
                /*
                 * With row-level security and a user using "COPY relation TO", we
                 * have to convert the "COPY relation TO" to a query-based COPY (eg:
-                * "COPY (SELECT * FROM relation) TO"), to allow the rewriter to add
-                * in any RLS clauses.
+                * "COPY (SELECT * FROM ONLY relation) TO"), to allow the rewriter to
+                * add in any RLS clauses.
                 *
                 * When this happens, we are passed in the relid of the originally
                 * found relation (which we have locked).  As the planner will look up
index b5f6eecba184e606fd852763fdbaf480d5e13aaa..fb86c13b1d6aa898f70f049aa7b834f56defeda1 100644 (file)
@@ -3691,6 +3691,42 @@ ERROR:  permission denied for table copy_rel_to
 SET row_security TO ON;
 COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied
 ERROR:  permission denied for table copy_rel_to
+-- Check behavior with a child table.
+RESET SESSION AUTHORIZATION;
+SET row_security TO ON;
+CREATE TABLE copy_rel_to_child () INHERITS (copy_rel_to);
+INSERT INTO copy_rel_to_child VALUES (1, 'one'), (2, 'two');
+-- Check COPY TO as Superuser/owner.
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ',';
+1,c4ca4238a0b923820dcc509a6f75849b
+SET row_security TO ON;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ',';
+1,c4ca4238a0b923820dcc509a6f75849b
+-- Check COPY TO as user with permissions.
+SET SESSION AUTHORIZATION regress_rls_bob;
+SET row_security TO OFF;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS
+ERROR:  query would be affected by row-level security policy for table "copy_rel_to"
+SET row_security TO ON;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
+-- Check COPY TO as user with permissions and BYPASSRLS
+SET SESSION AUTHORIZATION regress_rls_exempt_user;
+SET row_security TO OFF;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
+1,c4ca4238a0b923820dcc509a6f75849b
+SET row_security TO ON;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
+1,c4ca4238a0b923820dcc509a6f75849b
+-- Check COPY TO as user without permissions. SET row_security TO OFF;
+SET SESSION AUTHORIZATION regress_rls_carol;
+SET row_security TO OFF;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied
+ERROR:  permission denied for table copy_rel_to
+SET row_security TO ON;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied
+ERROR:  permission denied for table copy_rel_to
 -- Check COPY FROM as Superuser/owner.
 RESET SESSION AUTHORIZATION;
 SET row_security TO OFF;
@@ -3721,6 +3757,7 @@ ERROR:  permission denied for table copy_t
 RESET SESSION AUTHORIZATION;
 DROP TABLE copy_t;
 DROP TABLE copy_rel_to CASCADE;
+NOTICE:  drop cascades to table copy_rel_to_child
 -- Check WHERE CURRENT OF
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE TABLE current_check (currentid int, payload text, rlsuser text);
index febf3cc4cf10499eb3a0ccb1c16afe1509033a05..e0420d437871b617fe71b543287d925b488a073c 100644 (file)
@@ -1543,6 +1543,40 @@ COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied
 SET row_security TO ON;
 COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied
 
+-- Check behavior with a child table.
+RESET SESSION AUTHORIZATION;
+SET row_security TO ON;
+CREATE TABLE copy_rel_to_child () INHERITS (copy_rel_to);
+INSERT INTO copy_rel_to_child VALUES (1, 'one'), (2, 'two');
+
+-- Check COPY TO as Superuser/owner.
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ',';
+SET row_security TO ON;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ',';
+
+-- Check COPY TO as user with permissions.
+SET SESSION AUTHORIZATION regress_rls_bob;
+SET row_security TO OFF;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS
+SET row_security TO ON;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
+
+-- Check COPY TO as user with permissions and BYPASSRLS
+SET SESSION AUTHORIZATION regress_rls_exempt_user;
+SET row_security TO OFF;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
+SET row_security TO ON;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
+
+-- Check COPY TO as user without permissions. SET row_security TO OFF;
+SET SESSION AUTHORIZATION regress_rls_carol;
+SET row_security TO OFF;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied
+SET row_security TO ON;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied
+
 -- Check COPY FROM as Superuser/owner.
 RESET SESSION AUTHORIZATION;
 SET row_security TO OFF;