1
0
mirror of https://github.com/postgres/postgres.git synced 2025-12-19 17:02:53 +03:00

Fix security checks in selectivity estimation functions.

Commit e2d4ef8de8 (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). Commit 553d2ec271 then 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:
Dean Rasheed
2025-08-11 09:03:11 +01:00
parent b421223172
commit 22424953cd
12 changed files with 596 additions and 304 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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.

View File

@@ -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

View File

@@ -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;