Repository navigation
vacuum: the online rewrites run the owner's index code as the owner (#2053) - #2056
Open
OffgridwithJD wants to merge 1 commit into
Open
OffgridwithJD wants to merge 1 commit into
OffgridwithJD wants to merge 1 commit into
Conversation
…2053) compact_rewrite, recluster and recluster_hilbert put each moved row back into every index themselves (PgColumnarIndexInsertRow), which evaluates the table's index expressions and partial-index predicates. They ran as the caller. The maintenance daemon calls these verbs as the bootstrap superuser, and a superuser may call them on any table, so an unprivileged owner's function in an index ran as a superuser. After the caller's ownership check, each verb now switches to the table owner with SECURITY_RESTRICTED_OPERATION and a GUC nest level (plus RestrictSearchPath from PG 17), as core's cluster_rel and index_build do on every supported major, and restores the caller on return. Abort restores both on error. vacuum, vacuum_sorted and cluster rebuild through index_build, which already switched. The importers keep running as the caller, as an INSERT does. test_maintenance_runs_as_owner.py: a superuser runs each row-rewriting verb on a table owned by a plain role whose function is an index expression and a predicate. Every evaluation must report that role, security-restricted, and current_user and search_path must be the caller's afterwards. Red on main for the three rewrite verbs (user=postgres restricted=no). Six mutants each fail it. cluster_tests 5365 -> 5372. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2053.
The hole, measured
compact_rewrite,reclusterandrecluster_hilbertput each moved row back into every index themselves (PgColumnarIndexInsertRow,src/columnar_index.c:237/240). That evaluates the table's index expressions and partial-index predicates, and they ran as the caller.The maintenance daemon calls these verbs as the bootstrap superuser (
columnar_autovacuum.c), and a superuser may call them on any table. So an unprivileged table owner could put a function in an index and have it run as a superuser on the daemon's next pass.On main (PG 18), a superuser running each verb on a plain role's table got:
compact_rewriteuser=postgres restricted=nouser=pgc_mallory restricted=yesreclusteruser=postgres restricted=norecluster_hilbertuser=postgres restricted=novacuum,vacuum_sorted,cluster,cluster_hilbertindex_build)compact,expireThe two things you asked to confirm first
index_buildswitches on every supported major. Checked in the source of 15.19, 16.15, 17.11, 18.6 and 19rc1:index_build,validate_indexandreindex_indexcallSetUserIdAndSecContext(relowner, … | SECURITY_RESTRICTED_OPERATION)plusNewGUCNestLevel();RestrictSearchPath();cluster_reldoes the same.ShareUpdateExclusiveLock(online reads and writes continue). The switch happens after the lock is taken and the caller's ownership check has run, so the check still judges the caller.Change
src/columnar_vacuum.caddsswitch_to_ownerandrestore_caller, core's pattern:GetUserIdAndSecContext, thenSetUserIdAndSecContext(relowner, ctx | SECURITY_RESTRICTED_OPERATION);NewGUCNestLevel(), plusRestrictSearchPath()under#if PG_VERSION_NUM >= 170000;AtEOXact_GUCand the caller's identity are put back.They wrap the rewrite in
pgcolumnar_compact_rewriteandrecluster_verb. An error needs no restore: transaction and subtransaction abort put back the user, the security context and the GUC level.Deliberately not changed: the importers (
import_arrow,import_parquet). They run index code as the caller, as an ordinaryINSERTdoes, and core doesn't switch there either. Say if you want that revisited.Docs (
sql-reference.md, underreclusterandcompact_rewrite) and CHANGELOG are updated.Test:
test_maintenance_runs_as_owner.py(7 arms, TESTS.md §363)Fixture:
pgc_malloryowns the table and is nothing else. Herseen()function is one index's expression and another's predicate. It raises a NOTICE withcurrent_userand whether settingroleis refused, which is how a security-restricted operation shows from SQL. A block that always ends in an exception rolls the setting back.Each arm: a superuser runs one verb in a single transaction. The arm checks:
seen()ran at least once;user=pgc_mallory restricted=yes;current_userandsearch_pathare the caller's again.Mutants (PG 18): each one fails at least one arm.
compact_rewritecompact_rewriterecluster_verbrecluster,recluster_hilbertSECURITY_RESTRICTED_OPERATIONAtEOXact_GUC(search_pathstays restricted)Regression: the new file and every pytest file that calls
compact_rewriteorrecluster(42 files) pass on all five majors, so the verbs still work under the restriction and the restrictedsearch_path:Gate: gpush passed (guards, collection
cluster_tests5365 → 5372, census 9660/4894 unchanged, self-test PASSED). No ledger change.🤖 Generated with Claude Code