mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-08-31 00:57:22 +02:00
When a feature enumerates relations and runs functions associated with all found relations, the feature's user shall not need to trust every user having permission to create objects. BRIN-specific functionality in autovacuum neglected to account for this, as did pg_amcheck and CLUSTER. An attacker having permission to create non-temp objects in at least one schema could execute arbitrary SQL functions under the identity of the bootstrap superuser. CREATE INDEX (not a relation-enumerating operation) and REINDEX protected themselves too late. This change extends to the non-enumerating amcheck interface. Back-patch to v10 (all supported versions). Sergey Shinderuk, reviewed (in earlier versions) by Alexander Lakhin. Reported by Alexander Lakhin. Security: CVE-2022-1552
211 lines
6.8 KiB
Plaintext
211 lines
6.8 KiB
Plaintext
CREATE TABLE bttest_a(id int8);
|
|
CREATE TABLE bttest_b(id int8);
|
|
CREATE TABLE bttest_multi(id int8, data int8);
|
|
CREATE TABLE delete_test_table (a bigint, b bigint, c bigint, d bigint);
|
|
-- Stabalize tests
|
|
ALTER TABLE bttest_a SET (autovacuum_enabled = false);
|
|
ALTER TABLE bttest_b SET (autovacuum_enabled = false);
|
|
ALTER TABLE bttest_multi SET (autovacuum_enabled = false);
|
|
ALTER TABLE delete_test_table SET (autovacuum_enabled = false);
|
|
INSERT INTO bttest_a SELECT * FROM generate_series(1, 100000);
|
|
INSERT INTO bttest_b SELECT * FROM generate_series(100000, 1, -1);
|
|
INSERT INTO bttest_multi SELECT i, i%2 FROM generate_series(1, 100000) as i;
|
|
CREATE INDEX bttest_a_idx ON bttest_a USING btree (id) WITH (deduplicate_items = ON);
|
|
CREATE INDEX bttest_b_idx ON bttest_b USING btree (id);
|
|
CREATE UNIQUE INDEX bttest_multi_idx ON bttest_multi
|
|
USING btree (id) INCLUDE (data);
|
|
CREATE ROLE regress_bttest_role;
|
|
-- verify permissions are checked (error due to function not callable)
|
|
SET ROLE regress_bttest_role;
|
|
SELECT bt_index_check('bttest_a_idx'::regclass);
|
|
ERROR: permission denied for function bt_index_check
|
|
SELECT bt_index_parent_check('bttest_a_idx'::regclass);
|
|
ERROR: permission denied for function bt_index_parent_check
|
|
RESET ROLE;
|
|
-- we, intentionally, don't check relation permissions - it's useful
|
|
-- to run this cluster-wide with a restricted account, and as tested
|
|
-- above explicit permission has to be granted for that.
|
|
GRANT EXECUTE ON FUNCTION bt_index_check(regclass) TO regress_bttest_role;
|
|
GRANT EXECUTE ON FUNCTION bt_index_parent_check(regclass) TO regress_bttest_role;
|
|
GRANT EXECUTE ON FUNCTION bt_index_check(regclass, boolean) TO regress_bttest_role;
|
|
GRANT EXECUTE ON FUNCTION bt_index_parent_check(regclass, boolean) TO regress_bttest_role;
|
|
SET ROLE regress_bttest_role;
|
|
SELECT bt_index_check('bttest_a_idx');
|
|
bt_index_check
|
|
----------------
|
|
|
|
(1 row)
|
|
|
|
SELECT bt_index_parent_check('bttest_a_idx');
|
|
bt_index_parent_check
|
|
-----------------------
|
|
|
|
(1 row)
|
|
|
|
RESET ROLE;
|
|
-- verify plain tables are rejected (error)
|
|
SELECT bt_index_check('bttest_a');
|
|
ERROR: "bttest_a" is not an index
|
|
SELECT bt_index_parent_check('bttest_a');
|
|
ERROR: "bttest_a" is not an index
|
|
-- verify non-existing indexes are rejected (error)
|
|
SELECT bt_index_check(17);
|
|
ERROR: could not open relation with OID 17
|
|
SELECT bt_index_parent_check(17);
|
|
ERROR: could not open relation with OID 17
|
|
-- verify wrong index types are rejected (error)
|
|
BEGIN;
|
|
CREATE INDEX bttest_a_brin_idx ON bttest_a USING brin(id);
|
|
SELECT bt_index_parent_check('bttest_a_brin_idx');
|
|
ERROR: only B-Tree indexes are supported as targets for verification
|
|
DETAIL: Relation "bttest_a_brin_idx" is not a B-Tree index.
|
|
ROLLBACK;
|
|
-- normal check outside of xact
|
|
SELECT bt_index_check('bttest_a_idx');
|
|
bt_index_check
|
|
----------------
|
|
|
|
(1 row)
|
|
|
|
-- more expansive tests
|
|
SELECT bt_index_check('bttest_a_idx', true);
|
|
bt_index_check
|
|
----------------
|
|
|
|
(1 row)
|
|
|
|
SELECT bt_index_parent_check('bttest_b_idx', true);
|
|
bt_index_parent_check
|
|
-----------------------
|
|
|
|
(1 row)
|
|
|
|
BEGIN;
|
|
SELECT bt_index_check('bttest_a_idx');
|
|
bt_index_check
|
|
----------------
|
|
|
|
(1 row)
|
|
|
|
SELECT bt_index_parent_check('bttest_b_idx');
|
|
bt_index_parent_check
|
|
-----------------------
|
|
|
|
(1 row)
|
|
|
|
-- make sure we don't have any leftover locks
|
|
SELECT * FROM pg_locks
|
|
WHERE relation = ANY(ARRAY['bttest_a', 'bttest_a_idx', 'bttest_b', 'bttest_b_idx']::regclass[])
|
|
AND pid = pg_backend_pid();
|
|
locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction | pid | mode | granted | fastpath
|
|
----------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-----+------+---------+----------
|
|
(0 rows)
|
|
|
|
COMMIT;
|
|
-- Deduplication
|
|
TRUNCATE bttest_a;
|
|
INSERT INTO bttest_a SELECT 42 FROM generate_series(1, 2000);
|
|
SELECT bt_index_check('bttest_a_idx', true);
|
|
bt_index_check
|
|
----------------
|
|
|
|
(1 row)
|
|
|
|
-- normal check outside of xact for index with included columns
|
|
SELECT bt_index_check('bttest_multi_idx');
|
|
bt_index_check
|
|
----------------
|
|
|
|
(1 row)
|
|
|
|
-- more expansive tests for index with included columns
|
|
SELECT bt_index_parent_check('bttest_multi_idx', true, true);
|
|
bt_index_parent_check
|
|
-----------------------
|
|
|
|
(1 row)
|
|
|
|
-- repeat expansive tests for index built using insertions
|
|
TRUNCATE bttest_multi;
|
|
INSERT INTO bttest_multi SELECT i, i%2 FROM generate_series(1, 100000) as i;
|
|
SELECT bt_index_parent_check('bttest_multi_idx', true, true);
|
|
bt_index_parent_check
|
|
-----------------------
|
|
|
|
(1 row)
|
|
|
|
--
|
|
-- Test for multilevel page deletion/downlink present checks, and rootdescend
|
|
-- checks
|
|
--
|
|
INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,80000) i;
|
|
ALTER TABLE delete_test_table ADD PRIMARY KEY (a,b,c,d);
|
|
-- Delete most entries, and vacuum, deleting internal pages and creating "fast
|
|
-- root"
|
|
DELETE FROM delete_test_table WHERE a < 79990;
|
|
VACUUM delete_test_table;
|
|
SELECT bt_index_parent_check('delete_test_table_pkey', true);
|
|
bt_index_parent_check
|
|
-----------------------
|
|
|
|
(1 row)
|
|
|
|
--
|
|
-- BUG #15597: must not assume consistent input toasting state when forming
|
|
-- tuple. Bloom filter must fingerprint normalized index tuple representation.
|
|
--
|
|
CREATE TABLE toast_bug(buggy text);
|
|
ALTER TABLE toast_bug ALTER COLUMN buggy SET STORAGE extended;
|
|
CREATE INDEX toasty ON toast_bug(buggy);
|
|
-- pg_attribute entry for toasty.buggy (the index) will have plain storage:
|
|
UPDATE pg_attribute SET attstorage = 'p'
|
|
WHERE attrelid = 'toasty'::regclass AND attname = 'buggy';
|
|
-- Whereas pg_attribute entry for toast_bug.buggy (the table) still has extended storage:
|
|
SELECT attstorage FROM pg_attribute
|
|
WHERE attrelid = 'toast_bug'::regclass AND attname = 'buggy';
|
|
attstorage
|
|
------------
|
|
x
|
|
(1 row)
|
|
|
|
-- Insert compressible heap tuple (comfortably exceeds TOAST_TUPLE_THRESHOLD):
|
|
INSERT INTO toast_bug SELECT repeat('a', 2200);
|
|
-- Should not get false positive report of corruption:
|
|
SELECT bt_index_check('toasty', true);
|
|
bt_index_check
|
|
----------------
|
|
|
|
(1 row)
|
|
|
|
--
|
|
-- Check that index expressions and predicates are run as the table's owner
|
|
--
|
|
TRUNCATE bttest_a;
|
|
INSERT INTO bttest_a SELECT * FROM generate_series(1, 1000);
|
|
ALTER TABLE bttest_a OWNER TO regress_bttest_role;
|
|
-- A dummy index function checking current_user
|
|
CREATE FUNCTION ifun(int8) RETURNS int8 AS $$
|
|
BEGIN
|
|
ASSERT current_user = 'regress_bttest_role',
|
|
format('ifun(%s) called by %s', $1, current_user);
|
|
RETURN $1;
|
|
END;
|
|
$$ LANGUAGE plpgsql IMMUTABLE;
|
|
CREATE INDEX bttest_a_expr_idx ON bttest_a ((ifun(id) + ifun(0)))
|
|
WHERE ifun(id + 10) > ifun(10);
|
|
SELECT bt_index_check('bttest_a_expr_idx', true);
|
|
bt_index_check
|
|
----------------
|
|
|
|
(1 row)
|
|
|
|
-- cleanup
|
|
DROP TABLE bttest_a;
|
|
DROP TABLE bttest_b;
|
|
DROP TABLE bttest_multi;
|
|
DROP TABLE delete_test_table;
|
|
DROP TABLE toast_bug;
|
|
DROP FUNCTION ifun(int8);
|
|
DROP OWNED BY regress_bttest_role; -- permissions
|
|
DROP ROLE regress_bttest_role;
|