mirror of
https://github.com/postgres/postgres.git
synced 2025-12-19 17:02:53 +03:00
Fix security checks in selectivity estimation functions.
Commite2d4ef8de8(the fix for CVE-2017-7484) added security checks to the selectivity estimation functions to prevent them from running user-supplied operators on data obtained from pg_statistic if the user lacks privileges to select from the underlying table. In cases involving inheritance/partitioning, those checks were originally performed against the child RTE (which for plain inheritance might actually refer to the parent table). Commit553d2ec271then extended that to also check the parent RTE, allowing access if the user had permissions on either the parent or the child. It turns out, however, that doing any checks using the child RTE is incorrect, since securityQuals is set to NULL when creating an RTE for an inheritance child (whether it refers to the parent table or the child table), and therefore such checks do not correctly account for any RLS policies or security barrier views. Therefore, do the security checks using only the parent RTE. This is consistent with how RLS policies are applied, and the executor's ACL checks, both of which use only the parent table's permissions/policies. Similar checks are performed in the extended stats code, so update that in the same way, centralizing all the checks in a new function. In addition, note that these checks by themselves are insufficient to ensure that the user has access to the table's data because, in a query that goes via a view, they only check that the view owner has permissions on the underlying table, not that the current user has permissions on the view itself. In the selectivity estimation functions, there is no easy way to navigate from underlying tables to views, so add permissions checks for all views mentioned in the query to the planner startup code. If the user lacks permissions on a view, a permissions error will now be reported at planner-startup, and the selectivity estimation functions will not be run. Checking view permissions at planner-startup in this way is a little ugly, since the same checks will be repeated at executor-startup. Longer-term, it might be better to move all the permissions checks from the executor to the planner so that permissions errors can be reported sooner, instead of creating a plan that won't ever be run. However, such a change seems too far-reaching to be back-patched. Back-patch to all supported versions. In v13, there is the added complication that UPDATEs and DELETEs on inherited target tables are planned using inheritance_planner(), which plans each inheritance child table separately, so that the selectivity estimation functions do not know that they are dealing with a child table accessed via its parent. Handle that by checking access permissions on the top parent table at planner-startup, in the same way as we do for views. Any securityQuals on the top parent table are moved down to the child tables by inheritance_planner(), so they continue to be checked by the selectivity estimation functions. Author: Dean Rasheed <dean.a.rasheed@gmail.com> Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us> Reviewed-by: Noah Misch <noah@leadboat.com> Backpatch-through: 13 Security: CVE-2025-8713
This commit is contained in:
@@ -513,8 +513,6 @@ CREATE VIEW atest12v AS
|
||||
SELECT * FROM atest12 WHERE b <<< 5;
|
||||
CREATE VIEW atest12sbv WITH (security_barrier=true) AS
|
||||
SELECT * FROM atest12 WHERE b <<< 5;
|
||||
GRANT SELECT ON atest12v TO PUBLIC;
|
||||
GRANT SELECT ON atest12sbv TO PUBLIC;
|
||||
-- This plan should use nestloop, knowing that few rows will be selected.
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
|
||||
QUERY PLAN
|
||||
@@ -560,9 +558,18 @@ CREATE FUNCTION leak2(integer,integer) RETURNS boolean
|
||||
LANGUAGE plpgsql immutable;
|
||||
CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer,
|
||||
restrict = scalargtsel);
|
||||
-- This should not show any "leak" notices before failing.
|
||||
-- These should not show any "leak" notices before failing.
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0;
|
||||
ERROR: permission denied for table atest12
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM atest12v WHERE a >>> 0;
|
||||
ERROR: permission denied for view atest12v
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv WHERE a >>> 0;
|
||||
ERROR: permission denied for view atest12sbv
|
||||
-- Now regress_priv_user1 grants access to regress_priv_user2 via the views.
|
||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
||||
GRANT SELECT ON atest12v TO PUBLIC;
|
||||
GRANT SELECT ON atest12sbv TO PUBLIC;
|
||||
SET SESSION AUTHORIZATION regress_priv_user2;
|
||||
-- These plans should continue to use a nestloop, since they execute with the
|
||||
-- privileges of the view owner.
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
|
||||
|
||||
@@ -4506,7 +4506,7 @@ RESET SESSION AUTHORIZATION;
|
||||
DROP VIEW rls_view;
|
||||
DROP TABLE rls_tbl;
|
||||
DROP TABLE ref_tbl;
|
||||
-- Leaky operator test
|
||||
-- Leaky operator tests
|
||||
CREATE TABLE rls_tbl (a int);
|
||||
INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x;
|
||||
ANALYZE rls_tbl;
|
||||
@@ -4530,9 +4530,80 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_tbl WHERE a <<< 1000 or a <<< 900;
|
||||
One-Time Filter: false
|
||||
(2 rows)
|
||||
|
||||
RESET SESSION AUTHORIZATION;
|
||||
CREATE TABLE rls_child_tbl () INHERITS (rls_tbl);
|
||||
INSERT INTO rls_child_tbl SELECT x/10 FROM generate_series(1, 100) x;
|
||||
ANALYZE rls_child_tbl;
|
||||
CREATE TABLE rls_ptbl (a int) PARTITION BY RANGE (a);
|
||||
CREATE TABLE rls_part PARTITION OF rls_ptbl FOR VALUES FROM (-100) TO (100);
|
||||
INSERT INTO rls_ptbl SELECT x/10 FROM generate_series(1, 100) x;
|
||||
ANALYZE rls_ptbl, rls_part;
|
||||
ALTER TABLE rls_ptbl ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE rls_part ENABLE ROW LEVEL SECURITY;
|
||||
GRANT SELECT ON rls_ptbl TO regress_rls_alice;
|
||||
GRANT SELECT ON rls_part TO regress_rls_alice;
|
||||
CREATE POLICY p1 ON rls_tbl USING (a < 0);
|
||||
CREATE POLICY p2 ON rls_ptbl USING (a < 0);
|
||||
CREATE POLICY p3 ON rls_part USING (a < 0);
|
||||
SET SESSION AUTHORIZATION regress_rls_alice;
|
||||
SELECT * FROM rls_tbl WHERE a <<< 1000;
|
||||
a
|
||||
---
|
||||
(0 rows)
|
||||
|
||||
SELECT * FROM rls_child_tbl WHERE a <<< 1000;
|
||||
ERROR: permission denied for table rls_child_tbl
|
||||
SELECT * FROM rls_ptbl WHERE a <<< 1000;
|
||||
a
|
||||
---
|
||||
(0 rows)
|
||||
|
||||
SELECT * FROM rls_part WHERE a <<< 1000;
|
||||
a
|
||||
---
|
||||
(0 rows)
|
||||
|
||||
SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
|
||||
SELECT * FROM rls_tbl) t WHERE a <<< 1000;
|
||||
a
|
||||
---
|
||||
(0 rows)
|
||||
|
||||
SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
|
||||
SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
|
||||
ERROR: permission denied for table rls_child_tbl
|
||||
RESET SESSION AUTHORIZATION;
|
||||
REVOKE SELECT ON rls_tbl FROM regress_rls_alice;
|
||||
CREATE VIEW rls_tbl_view AS SELECT * FROM rls_tbl;
|
||||
ALTER TABLE rls_child_tbl ENABLE ROW LEVEL SECURITY;
|
||||
GRANT SELECT ON rls_child_tbl TO regress_rls_alice;
|
||||
CREATE POLICY p4 ON rls_child_tbl USING (a < 0);
|
||||
SET SESSION AUTHORIZATION regress_rls_alice;
|
||||
SELECT * FROM rls_tbl WHERE a <<< 1000;
|
||||
ERROR: permission denied for table rls_tbl
|
||||
SELECT * FROM rls_tbl_view WHERE a <<< 1000;
|
||||
ERROR: permission denied for view rls_tbl_view
|
||||
SELECT * FROM rls_child_tbl WHERE a <<< 1000;
|
||||
a
|
||||
---
|
||||
(0 rows)
|
||||
|
||||
SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
|
||||
SELECT * FROM rls_tbl) t WHERE a <<< 1000;
|
||||
ERROR: permission denied for table rls_tbl
|
||||
SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
|
||||
SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
|
||||
a
|
||||
---
|
||||
(0 rows)
|
||||
|
||||
DROP OPERATOR <<< (int, int);
|
||||
DROP FUNCTION op_leak(int, int);
|
||||
RESET SESSION AUTHORIZATION;
|
||||
DROP TABLE rls_part;
|
||||
DROP TABLE rls_ptbl;
|
||||
DROP TABLE rls_child_tbl;
|
||||
DROP VIEW rls_tbl_view;
|
||||
DROP TABLE rls_tbl;
|
||||
-- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects
|
||||
SET SESSION AUTHORIZATION regress_rls_alice;
|
||||
|
||||
@@ -3275,9 +3275,17 @@ CREATE FUNCTION op_leak(int, int) RETURNS bool
|
||||
LANGUAGE plpgsql;
|
||||
CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
|
||||
restrict = scalarltsel);
|
||||
CREATE FUNCTION op_leak(record, record) RETURNS bool
|
||||
AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END'
|
||||
LANGUAGE plpgsql;
|
||||
CREATE OPERATOR <<< (procedure = op_leak, leftarg = record, rightarg = record,
|
||||
restrict = scalarltsel);
|
||||
SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
|
||||
ERROR: permission denied for table priv_test_tbl
|
||||
SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0;
|
||||
SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0; -- Permission denied
|
||||
ERROR: permission denied for table priv_test_tbl
|
||||
SELECT * FROM tststats.priv_test_tbl t
|
||||
WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied
|
||||
ERROR: permission denied for table priv_test_tbl
|
||||
DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
|
||||
ERROR: permission denied for table priv_test_tbl
|
||||
@@ -3298,10 +3306,17 @@ SELECT * FROM tststats.priv_test_view WHERE a <<< 0 OR b <<< 0; -- Should not le
|
||||
---+---
|
||||
(0 rows)
|
||||
|
||||
SELECT * FROM tststats.priv_test_view t
|
||||
WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
|
||||
a | b
|
||||
---+---
|
||||
(0 rows)
|
||||
|
||||
DELETE FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak
|
||||
-- Grant table access, but hide all data with RLS
|
||||
RESET SESSION AUTHORIZATION;
|
||||
ALTER TABLE tststats.priv_test_tbl ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY priv_test_tbl_pol ON tststats.priv_test_tbl USING (2 * a < 0);
|
||||
GRANT SELECT, DELETE ON tststats.priv_test_tbl TO regress_stats_user1;
|
||||
-- Should now have direct table access, but see nothing and leak nothing
|
||||
SET SESSION AUTHORIZATION regress_stats_user1;
|
||||
@@ -3310,12 +3325,57 @@ SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not le
|
||||
---+---
|
||||
(0 rows)
|
||||
|
||||
SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0;
|
||||
SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0; -- Should not leak
|
||||
a | b
|
||||
---+---
|
||||
(0 rows)
|
||||
|
||||
SELECT * FROM tststats.priv_test_tbl t
|
||||
WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
|
||||
a | b
|
||||
---+---
|
||||
(0 rows)
|
||||
|
||||
DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
|
||||
-- Create plain inheritance parent table with no access permissions
|
||||
RESET SESSION AUTHORIZATION;
|
||||
CREATE TABLE tststats.priv_test_parent_tbl (a int, b int);
|
||||
ALTER TABLE tststats.priv_test_tbl INHERIT tststats.priv_test_parent_tbl;
|
||||
-- Should not have access to parent, and should leak nothing
|
||||
SET SESSION AUTHORIZATION regress_stats_user1;
|
||||
SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
|
||||
ERROR: permission denied for table priv_test_parent_tbl
|
||||
SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 OR b <<< 0; -- Permission denied
|
||||
ERROR: permission denied for table priv_test_parent_tbl
|
||||
SELECT * FROM tststats.priv_test_parent_tbl t
|
||||
WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied
|
||||
ERROR: permission denied for table priv_test_parent_tbl
|
||||
DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
|
||||
ERROR: permission denied for table priv_test_parent_tbl
|
||||
-- Grant table access to parent, but hide all data with RLS
|
||||
RESET SESSION AUTHORIZATION;
|
||||
ALTER TABLE tststats.priv_test_parent_tbl ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY priv_test_parent_tbl_pol ON tststats.priv_test_parent_tbl USING (2 * a < 0);
|
||||
GRANT SELECT, DELETE ON tststats.priv_test_parent_tbl TO regress_stats_user1;
|
||||
-- Should now have direct table access to parent, but see nothing and leak nothing
|
||||
SET SESSION AUTHORIZATION regress_stats_user1;
|
||||
SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
|
||||
a | b
|
||||
---+---
|
||||
(0 rows)
|
||||
|
||||
SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 OR b <<< 0; -- Should not leak
|
||||
a | b
|
||||
---+---
|
||||
(0 rows)
|
||||
|
||||
SELECT * FROM tststats.priv_test_parent_tbl t
|
||||
WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
|
||||
a | b
|
||||
---+---
|
||||
(0 rows)
|
||||
|
||||
DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
|
||||
-- privilege checks for pg_stats_ext and pg_stats_ext_exprs
|
||||
RESET SESSION AUTHORIZATION;
|
||||
CREATE TABLE stats_ext_tbl (id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, col TEXT);
|
||||
@@ -3361,11 +3421,14 @@ SELECT statistics_name, most_common_vals FROM pg_stats_ext_exprs x
|
||||
-- Tidy up
|
||||
DROP OPERATOR <<< (int, int);
|
||||
DROP FUNCTION op_leak(int, int);
|
||||
DROP OPERATOR <<< (record, record);
|
||||
DROP FUNCTION op_leak(record, record);
|
||||
RESET SESSION AUTHORIZATION;
|
||||
DROP TABLE stats_ext_tbl;
|
||||
DROP SCHEMA tststats CASCADE;
|
||||
NOTICE: drop cascades to 2 other objects
|
||||
DETAIL: drop cascades to table tststats.priv_test_tbl
|
||||
NOTICE: drop cascades to 3 other objects
|
||||
DETAIL: drop cascades to table tststats.priv_test_parent_tbl
|
||||
drop cascades to table tststats.priv_test_tbl
|
||||
drop cascades to view tststats.priv_test_view
|
||||
DROP USER regress_stats_user1;
|
||||
CREATE TABLE grouping_unique (x integer);
|
||||
|
||||
@@ -346,8 +346,6 @@ CREATE VIEW atest12v AS
|
||||
SELECT * FROM atest12 WHERE b <<< 5;
|
||||
CREATE VIEW atest12sbv WITH (security_barrier=true) AS
|
||||
SELECT * FROM atest12 WHERE b <<< 5;
|
||||
GRANT SELECT ON atest12v TO PUBLIC;
|
||||
GRANT SELECT ON atest12sbv TO PUBLIC;
|
||||
|
||||
-- This plan should use nestloop, knowing that few rows will be selected.
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
|
||||
@@ -369,8 +367,16 @@ CREATE FUNCTION leak2(integer,integer) RETURNS boolean
|
||||
CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer,
|
||||
restrict = scalargtsel);
|
||||
|
||||
-- This should not show any "leak" notices before failing.
|
||||
-- These should not show any "leak" notices before failing.
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0;
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM atest12v WHERE a >>> 0;
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv WHERE a >>> 0;
|
||||
|
||||
-- Now regress_priv_user1 grants access to regress_priv_user2 via the views.
|
||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
||||
GRANT SELECT ON atest12v TO PUBLIC;
|
||||
GRANT SELECT ON atest12sbv TO PUBLIC;
|
||||
SET SESSION AUTHORIZATION regress_priv_user2;
|
||||
|
||||
-- These plans should continue to use a nestloop, since they execute with the
|
||||
-- privileges of the view owner.
|
||||
|
||||
@@ -2189,7 +2189,7 @@ DROP VIEW rls_view;
|
||||
DROP TABLE rls_tbl;
|
||||
DROP TABLE ref_tbl;
|
||||
|
||||
-- Leaky operator test
|
||||
-- Leaky operator tests
|
||||
CREATE TABLE rls_tbl (a int);
|
||||
INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x;
|
||||
ANALYZE rls_tbl;
|
||||
@@ -2205,9 +2205,58 @@ CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
|
||||
restrict = scalarltsel);
|
||||
SELECT * FROM rls_tbl WHERE a <<< 1000;
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM rls_tbl WHERE a <<< 1000 or a <<< 900;
|
||||
RESET SESSION AUTHORIZATION;
|
||||
|
||||
CREATE TABLE rls_child_tbl () INHERITS (rls_tbl);
|
||||
INSERT INTO rls_child_tbl SELECT x/10 FROM generate_series(1, 100) x;
|
||||
ANALYZE rls_child_tbl;
|
||||
|
||||
CREATE TABLE rls_ptbl (a int) PARTITION BY RANGE (a);
|
||||
CREATE TABLE rls_part PARTITION OF rls_ptbl FOR VALUES FROM (-100) TO (100);
|
||||
INSERT INTO rls_ptbl SELECT x/10 FROM generate_series(1, 100) x;
|
||||
ANALYZE rls_ptbl, rls_part;
|
||||
|
||||
ALTER TABLE rls_ptbl ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE rls_part ENABLE ROW LEVEL SECURITY;
|
||||
GRANT SELECT ON rls_ptbl TO regress_rls_alice;
|
||||
GRANT SELECT ON rls_part TO regress_rls_alice;
|
||||
CREATE POLICY p1 ON rls_tbl USING (a < 0);
|
||||
CREATE POLICY p2 ON rls_ptbl USING (a < 0);
|
||||
CREATE POLICY p3 ON rls_part USING (a < 0);
|
||||
|
||||
SET SESSION AUTHORIZATION regress_rls_alice;
|
||||
SELECT * FROM rls_tbl WHERE a <<< 1000;
|
||||
SELECT * FROM rls_child_tbl WHERE a <<< 1000;
|
||||
SELECT * FROM rls_ptbl WHERE a <<< 1000;
|
||||
SELECT * FROM rls_part WHERE a <<< 1000;
|
||||
SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
|
||||
SELECT * FROM rls_tbl) t WHERE a <<< 1000;
|
||||
SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
|
||||
SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
|
||||
RESET SESSION AUTHORIZATION;
|
||||
|
||||
REVOKE SELECT ON rls_tbl FROM regress_rls_alice;
|
||||
CREATE VIEW rls_tbl_view AS SELECT * FROM rls_tbl;
|
||||
|
||||
ALTER TABLE rls_child_tbl ENABLE ROW LEVEL SECURITY;
|
||||
GRANT SELECT ON rls_child_tbl TO regress_rls_alice;
|
||||
CREATE POLICY p4 ON rls_child_tbl USING (a < 0);
|
||||
|
||||
SET SESSION AUTHORIZATION regress_rls_alice;
|
||||
SELECT * FROM rls_tbl WHERE a <<< 1000;
|
||||
SELECT * FROM rls_tbl_view WHERE a <<< 1000;
|
||||
SELECT * FROM rls_child_tbl WHERE a <<< 1000;
|
||||
SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
|
||||
SELECT * FROM rls_tbl) t WHERE a <<< 1000;
|
||||
SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
|
||||
SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
|
||||
DROP OPERATOR <<< (int, int);
|
||||
DROP FUNCTION op_leak(int, int);
|
||||
RESET SESSION AUTHORIZATION;
|
||||
DROP TABLE rls_part;
|
||||
DROP TABLE rls_ptbl;
|
||||
DROP TABLE rls_child_tbl;
|
||||
DROP VIEW rls_tbl_view;
|
||||
DROP TABLE rls_tbl;
|
||||
|
||||
-- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects
|
||||
|
||||
@@ -1647,8 +1647,15 @@ CREATE FUNCTION op_leak(int, int) RETURNS bool
|
||||
LANGUAGE plpgsql;
|
||||
CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
|
||||
restrict = scalarltsel);
|
||||
CREATE FUNCTION op_leak(record, record) RETURNS bool
|
||||
AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END'
|
||||
LANGUAGE plpgsql;
|
||||
CREATE OPERATOR <<< (procedure = op_leak, leftarg = record, rightarg = record,
|
||||
restrict = scalarltsel);
|
||||
SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
|
||||
SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0;
|
||||
SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0; -- Permission denied
|
||||
SELECT * FROM tststats.priv_test_tbl t
|
||||
WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied
|
||||
DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
|
||||
|
||||
-- Grant access via a security barrier view, but hide all data
|
||||
@@ -1661,19 +1668,51 @@ GRANT SELECT, DELETE ON tststats.priv_test_view TO regress_stats_user1;
|
||||
SET SESSION AUTHORIZATION regress_stats_user1;
|
||||
SELECT * FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak
|
||||
SELECT * FROM tststats.priv_test_view WHERE a <<< 0 OR b <<< 0; -- Should not leak
|
||||
SELECT * FROM tststats.priv_test_view t
|
||||
WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
|
||||
DELETE FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak
|
||||
|
||||
-- Grant table access, but hide all data with RLS
|
||||
RESET SESSION AUTHORIZATION;
|
||||
ALTER TABLE tststats.priv_test_tbl ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY priv_test_tbl_pol ON tststats.priv_test_tbl USING (2 * a < 0);
|
||||
GRANT SELECT, DELETE ON tststats.priv_test_tbl TO regress_stats_user1;
|
||||
|
||||
-- Should now have direct table access, but see nothing and leak nothing
|
||||
SET SESSION AUTHORIZATION regress_stats_user1;
|
||||
SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
|
||||
SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0;
|
||||
SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0; -- Should not leak
|
||||
SELECT * FROM tststats.priv_test_tbl t
|
||||
WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
|
||||
DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
|
||||
|
||||
-- Create plain inheritance parent table with no access permissions
|
||||
RESET SESSION AUTHORIZATION;
|
||||
CREATE TABLE tststats.priv_test_parent_tbl (a int, b int);
|
||||
ALTER TABLE tststats.priv_test_tbl INHERIT tststats.priv_test_parent_tbl;
|
||||
|
||||
-- Should not have access to parent, and should leak nothing
|
||||
SET SESSION AUTHORIZATION regress_stats_user1;
|
||||
SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
|
||||
SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 OR b <<< 0; -- Permission denied
|
||||
SELECT * FROM tststats.priv_test_parent_tbl t
|
||||
WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied
|
||||
DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
|
||||
|
||||
-- Grant table access to parent, but hide all data with RLS
|
||||
RESET SESSION AUTHORIZATION;
|
||||
ALTER TABLE tststats.priv_test_parent_tbl ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY priv_test_parent_tbl_pol ON tststats.priv_test_parent_tbl USING (2 * a < 0);
|
||||
GRANT SELECT, DELETE ON tststats.priv_test_parent_tbl TO regress_stats_user1;
|
||||
|
||||
-- Should now have direct table access to parent, but see nothing and leak nothing
|
||||
SET SESSION AUTHORIZATION regress_stats_user1;
|
||||
SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
|
||||
SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 OR b <<< 0; -- Should not leak
|
||||
SELECT * FROM tststats.priv_test_parent_tbl t
|
||||
WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
|
||||
DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
|
||||
|
||||
-- privilege checks for pg_stats_ext and pg_stats_ext_exprs
|
||||
RESET SESSION AUTHORIZATION;
|
||||
CREATE TABLE stats_ext_tbl (id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, col TEXT);
|
||||
@@ -1703,6 +1742,8 @@ SELECT statistics_name, most_common_vals FROM pg_stats_ext_exprs x
|
||||
-- Tidy up
|
||||
DROP OPERATOR <<< (int, int);
|
||||
DROP FUNCTION op_leak(int, int);
|
||||
DROP OPERATOR <<< (record, record);
|
||||
DROP FUNCTION op_leak(record, record);
|
||||
RESET SESSION AUTHORIZATION;
|
||||
DROP TABLE stats_ext_tbl;
|
||||
DROP SCHEMA tststats CASCADE;
|
||||
|
||||
Reference in New Issue
Block a user