summaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
authorNathan Bossart <nathan@postgresql.org>2024-05-06 09:00:13 -0500
committerNathan Bossart <nathan@postgresql.org>2024-05-06 09:00:13 -0500
commit9cc2b62894de6a8b3d78d20bcd1a6647a7553a6c (patch)
treef5a878c70fbc5ad057b1910eadc954fb756752bc /src/test
parent3672c6cdfd7cb2cb82837cf4a341bf7c0a60bd59 (diff)
Fix privilege checks in pg_stats_ext and pg_stats_ext_exprs.
The catalog view pg_stats_ext fails to consider privileges for expression statistics. The catalog view pg_stats_ext_exprs fails to consider privileges and row-level security policies. To fix, restrict the data in these views to table owners or roles that inherit privileges of the table owner. It may be possible to apply less restrictive privilege checks in some cases, but that is left as a future exercise. Furthermore, for pg_stats_ext_exprs, do not return data for tables with row-level security enabled, as is already done for pg_stats_ext. On the back-branches, a fix-CVE-2024-4317.sql script is provided that will install into the "share" directory. This file can be used to apply the fix to existing clusters. Bumps catversion on 'master' branch only. Reported-by: Lukas Fittl Reviewed-by: Noah Misch, Tomas Vondra, Tom Lane Security: CVE-2024-4317 Backpatch-through: 14
Diffstat (limited to 'src/test')
-rw-r--r--src/test/regress/expected/rules.out8
-rw-r--r--src/test/regress/expected/stats_ext.out43
-rw-r--r--src/test/regress/sql/stats_ext.sql27
3 files changed, 73 insertions, 5 deletions
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 225204c3a46..0a1e17ef5b5 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2453,10 +2453,7 @@ pg_stats_ext| SELECT cn.nspname AS schemaname,
array_agg(pg_mcv_list_items.frequency) AS most_common_freqs,
array_agg(pg_mcv_list_items.base_frequency) AS most_common_base_freqs
FROM pg_mcv_list_items(sd.stxdmcv) pg_mcv_list_items(index, "values", nulls, frequency, base_frequency)) m ON ((sd.stxdmcv IS NOT NULL)))
- WHERE ((NOT (EXISTS ( SELECT 1
- FROM (unnest(s.stxkeys) k(k)
- JOIN pg_attribute a ON (((a.attrelid = s.stxrelid) AND (a.attnum = k.k))))
- WHERE (NOT has_column_privilege(c.oid, a.attnum, 'select'::text))))) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+ WHERE (pg_has_role(c.relowner, 'USAGE'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
pg_stats_ext_exprs| SELECT cn.nspname AS schemaname,
c.relname AS tablename,
sn.nspname AS statistics_schemaname,
@@ -2529,7 +2526,8 @@ pg_stats_ext_exprs| SELECT cn.nspname AS schemaname,
LEFT JOIN pg_namespace cn ON ((cn.oid = c.relnamespace)))
LEFT JOIN pg_namespace sn ON ((sn.oid = s.stxnamespace)))
JOIN LATERAL ( SELECT unnest(pg_get_statisticsobjdef_expressions(s.oid)) AS expr,
- unnest(sd.stxdexpr) AS a) stat ON ((stat.expr IS NOT NULL)));
+ unnest(sd.stxdexpr) AS a) stat ON ((stat.expr IS NOT NULL)))
+ WHERE (pg_has_role(c.relowner, 'USAGE'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
pg_tables| SELECT n.nspname AS schemaname,
c.relname AS tablename,
pg_get_userbyid(c.relowner) AS tableowner,
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 67cae4a83db..69e5704e21e 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -3266,10 +3266,53 @@ SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not le
(0 rows)
DELETE FROM tststats.priv_test_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);
+INSERT INTO stats_ext_tbl (col) VALUES ('secret'), ('secret'), ('very secret');
+CREATE STATISTICS s_col ON id, col FROM stats_ext_tbl;
+CREATE STATISTICS s_expr ON mod(id, 2), lower(col) FROM stats_ext_tbl;
+ANALYZE stats_ext_tbl;
+-- unprivileged role should not have access
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT statistics_name, most_common_vals FROM pg_stats_ext x
+ WHERE tablename = 'stats_ext_tbl' ORDER BY ROW(x.*);
+ statistics_name | most_common_vals
+-----------------+------------------
+(0 rows)
+
+SELECT statistics_name, most_common_vals FROM pg_stats_ext_exprs x
+ WHERE tablename = 'stats_ext_tbl' ORDER BY ROW(x.*);
+ statistics_name | most_common_vals
+-----------------+------------------
+(0 rows)
+
+-- give unprivileged role ownership of table
+RESET SESSION AUTHORIZATION;
+ALTER TABLE stats_ext_tbl OWNER TO regress_stats_user1;
+-- unprivileged role should now have access
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT statistics_name, most_common_vals FROM pg_stats_ext x
+ WHERE tablename = 'stats_ext_tbl' ORDER BY ROW(x.*);
+ statistics_name | most_common_vals
+-----------------+-------------------------------------------
+ s_col | {{1,secret},{2,secret},{3,"very secret"}}
+ s_expr | {{0,secret},{1,secret},{1,"very secret"}}
+(2 rows)
+
+SELECT statistics_name, most_common_vals FROM pg_stats_ext_exprs x
+ WHERE tablename = 'stats_ext_tbl' ORDER BY ROW(x.*);
+ statistics_name | most_common_vals
+-----------------+------------------
+ s_expr | {secret}
+ s_expr | {1}
+(2 rows)
+
-- Tidy up
DROP OPERATOR <<< (int, int);
DROP FUNCTION op_leak(int, int);
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
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index f0ee4159720..76291efa4f9 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -1649,9 +1649,36 @@ SET SESSION AUTHORIZATION regress_stats_user1;
SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
DELETE FROM tststats.priv_test_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);
+INSERT INTO stats_ext_tbl (col) VALUES ('secret'), ('secret'), ('very secret');
+CREATE STATISTICS s_col ON id, col FROM stats_ext_tbl;
+CREATE STATISTICS s_expr ON mod(id, 2), lower(col) FROM stats_ext_tbl;
+ANALYZE stats_ext_tbl;
+
+-- unprivileged role should not have access
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT statistics_name, most_common_vals FROM pg_stats_ext x
+ WHERE tablename = 'stats_ext_tbl' ORDER BY ROW(x.*);
+SELECT statistics_name, most_common_vals FROM pg_stats_ext_exprs x
+ WHERE tablename = 'stats_ext_tbl' ORDER BY ROW(x.*);
+
+-- give unprivileged role ownership of table
+RESET SESSION AUTHORIZATION;
+ALTER TABLE stats_ext_tbl OWNER TO regress_stats_user1;
+
+-- unprivileged role should now have access
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT statistics_name, most_common_vals FROM pg_stats_ext x
+ WHERE tablename = 'stats_ext_tbl' ORDER BY ROW(x.*);
+SELECT statistics_name, most_common_vals FROM pg_stats_ext_exprs x
+ WHERE tablename = 'stats_ext_tbl' ORDER BY ROW(x.*);
+
-- Tidy up
DROP OPERATOR <<< (int, int);
DROP FUNCTION op_leak(int, int);
RESET SESSION AUTHORIZATION;
+DROP TABLE stats_ext_tbl;
DROP SCHEMA tststats CASCADE;
DROP USER regress_stats_user1;