1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

Empty search_path in amcheck.

A grantee of amcheck function EXECUTE privilege could execute arbitrary
functions as the owners of expression indexes that depend on the search
path.  An expression like (lower(col)) was not vulnerable, because
lower() is resolved at CREATE INDEX time.  However, an expression
calling an sql-language or plpgsql-language function often was
vulnerable, even if it used search_path only to find objects in
pg_catalog.  The amcheck documentation has been warning about data
disclosure after such a GRANT, not about function execution.

This might cause new amcheck errors when index expressions rely on a
broader search_path.  Such indexes have seen errors during auto-analyze
since CVE-2018-1058 commit 582edc369c, and
v17 amcheck always worked this way.  Hence, the risk is low.

Leave a comment on the one other sandbox entrance that doesn't empty
search_path.  In its case, the choice was valid.

Back-patch to v14 (all supported versions), but v17 was safe already.
Commit 2af07e2f74 (v17) unintentionally
blocked the attack, and commit d70b17636d
(v18) unintentionally removed that protection.  Hence, this adds to v17
just a test and a comment.  While emptying search_path became more
widespread in commit 2af07e2f74 (v17),
none of its other changes blocked an attack available in v16, even when
considering GRANT.  For example, brin_summarize_range() has had an owner
check that GRANT does not override.

Reported-by: 王跃林 <violin0613@tju.edu.cn>
Reported-by: Jacob Brazeal <jacob.brazeal@gmail.com>
Backpatch-through: 14
Security: CVE-2026-14673
This commit is contained in:
Noah Misch
2026-08-10 06:38:30 -07:00
parent 44ea6764be
commit e43e74756b
3 changed files with 32 additions and 5 deletions

View File

@@ -178,7 +178,8 @@ SELECT bt_index_check('toasty', true);
(1 row)
--
-- Check that index expressions and predicates are run as the table's owner
-- Check that index expressions and predicates are run as the table's owner,
-- with empty search_path
--
TRUNCATE bttest_a;
INSERT INTO bttest_a SELECT * FROM generate_series(1, 1000);
@@ -186,19 +187,31 @@ 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',
ASSERT current_setting('search_path') NOT LIKE '%preempt%',
format('ifun(%s) called with current_schemas %s, search_path %s',
$1, current_schemas(true), current_setting('search_path'));
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);
BEGIN;
SET LOCAL check_function_bodies = off;
CREATE SCHEMA preempt;
GRANT USAGE ON SCHEMA preempt TO regress_bttest_role;
SET LOCAL search_path = preempt, pg_catalog, public;
CREATE FUNCTION "current_user"() RETURNS name AS $$
broken
$$ LANGUAGE sql STABLE PARALLEL SAFE STRICT;
SELECT bt_index_check('bttest_a_expr_idx', true);
bt_index_check
----------------
(1 row)
ROLLBACK;
-- Check support of both 1B and 4B header sizes of short varlena datum
CREATE TABLE varlena_bug (v text);
ALTER TABLE varlena_bug ALTER column v SET storage plain;

View File

@@ -116,7 +116,8 @@ INSERT INTO toast_bug SELECT repeat('a', 2200);
SELECT bt_index_check('toasty', true);
--
-- Check that index expressions and predicates are run as the table's owner
-- Check that index expressions and predicates are run as the table's owner,
-- with empty search_path
--
TRUNCATE bttest_a;
INSERT INTO bttest_a SELECT * FROM generate_series(1, 1000);
@@ -124,7 +125,10 @@ 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',
ASSERT current_setting('search_path') NOT LIKE '%preempt%',
format('ifun(%s) called with current_schemas %s, search_path %s',
$1, current_schemas(true), current_setting('search_path'));
ASSERT "current_user"() = 'regress_bttest_role',
format('ifun(%s) called by %s', $1, current_user);
RETURN $1;
END;
@@ -132,8 +136,16 @@ $$ LANGUAGE plpgsql IMMUTABLE;
CREATE INDEX bttest_a_expr_idx ON bttest_a ((ifun(id) + ifun(0)))
WHERE ifun(id + 10) > ifun(10);
BEGIN;
SET LOCAL check_function_bodies = off;
CREATE SCHEMA preempt;
GRANT USAGE ON SCHEMA preempt TO regress_bttest_role;
SET LOCAL search_path = preempt, pg_catalog, public;
CREATE FUNCTION "current_user"() RETURNS name AS $$
broken
$$ LANGUAGE sql STABLE PARALLEL SAFE STRICT;
SELECT bt_index_check('bttest_a_expr_idx', true);
ROLLBACK;
-- Check support of both 1B and 4B header sizes of short varlena datum
CREATE TABLE varlena_bug (v text);

View File

@@ -285,6 +285,8 @@ bt_index_check_internal(Oid indrelid, bool parentcheck, bool heapallindexed,
SetUserIdAndSecContext(heaprel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
set_config_option("search_path", "pg_catalog, pg_temp", PGC_USERSET,
PGC_S_SESSION, GUC_ACTION_SAVE, true, 0, false);
}
else
{