Repository navigation
test: pytest twins for vacuum_lock_privilege, vm_privilege and debug_hook_privilege; vm's owner controls get their own table; seeds the ledger (#1341) - #1381
Conversation
jdatcmd
left a comment
There was a problem hiding this comment.
Name parity, the vm fix and the ledger arithmetic are sound, but the PR carries a check that can't fail into the new twin. Reviewed on PG18 at d162def.
What works:
- Green: vm_privilege 30/30, vacuum_lock_privilege 18/18, debug_hook_privilege 10/10; pytest 58 checks pass;
compare_to_bash: every property covered. - Lock order, really tested: with
PgColumnarRequireTableOwnerByOidmoved aftertable_openinpgcolumnar_vacuum, both twins fail:
FAIL vacuum refuses a non-owner before taking the exclusive lock: got [locktimeout] want [owner] - The vm fix was needed. With the owner check deleted from
pgcolumnar_vm_selftest, main's suite also failsthe owner can still call vm_selftest: got [f] want [t]. The attack had already set the bit on the shared table. Withvmownthat control stays green and only the real four fail.
What to change:
- "the refused caller left no lock request queued on victim" can't fail. It runs after every probe session has timed out and exited (
vacuum_lock_privilege.sh:125). Under the mutation above, vacuum was queued, and the check still passed in both twins. Either look atpg_lockswhile a probe is still waiting, or remove the check. It is inherited from the shell, but this PR seeds it in the twin, TESTS.md and the ledger as a live check. - "premise: the owner can call the advance hook (no-op advance)" is an owner control. With the hook refusing every caller it goes red:
got [42501] want []. Rename it in both twins. Three vm_privilege premises look the same (no all-visible bit yet; 10000 live rows; more than one block): each depends on product behaviour. Unverified.
Suspected, not run: the shell holder sleeps a fixed 30 s. Under a mutant that moves every check after its lock, eight probes × the 4 s timeout outlast it, and the last probes run uncontended. Holding the lock until an explicit release, as the pytest twin does, would close it.
Minor: in the pytest debug twin, "the gated table still reads" raises a psycopg error under its mutant, instead of failing under its own name.
Outside this PR: pgcolumnar.truncate takes ShareUpdateExclusiveLock before its owner check (src/columnar_vacuum.c:2917, :2927). A probe in a copy of this suite gave got [locktimeout] want [owner], and none of these suites covers it. I'll file that as an issue.
…ueued-lock arm is watched while a refused probe runs, the holder holds until released, and four product checks lose the premise prefix (commandprompt#1341) - "the refused caller left no lock request queued" ran after every probe had exited, so it could not fail. It now polls pg_locks while a refused vacuum probe runs, in both twins, renamed to say so. - The shell holder slept a fixed 30 s; under a mutant that queues every probe, the eighth ran uncontended (measured: compact's arm stayed green on main). It now holds until released by a tag on its statement, and a premise asserts the lock is still held after the last probe. - The advance-hook owner call and vm's three columnar fixture checks read the code under test, so they are arms; the shell's owner call expects "ok", not an empty SQLSTATE. - The debug twin's gated-table read reports -1 when unreadable instead of raising. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho
d162def to
e697326
Compare
|
Thanks for the review. All four points are taken, and the suspected one is now measured. The branch is rebuilt on f601281 as two commits:
The test files are byte-identical to the tree the evidence below ran on. 1. The queued-lock arm couldn't failIt now polls
2. Premises that read product codeThese four are now arms in both twins, renamed in place in the ledger so their history is kept:
3. The 30-second holder (your suspicion is correct)Mutant: every maintenance entry point checks ownership after taking its lock (7 sites, covering all 8 functions). On main, 7 of the 8 contention arms go red, but compact's stays green: the holder has expired by the 8th probe. The holder now holds until it's released by a tag on its statement. A new premise checks the lock is still held after the last probe. On the branch, all 8 go red, plus the type-disclosure arm and the queued-lock arm. 4. The debug twin's crashThe gated-table read now reports EvidenceClean: vacuum_lock 19/19, debug_hook 10/10, vm 30/30, and pytest 3 passed, on each of 15, 16, 17, 18 and 19. The ledger gains the one new premise, and 14 rows are dated for the first time by those four mutations: never 1944 + 1 − 14 = 1931, dated 588, rows 2519. The gate is clean against origin/main. Guard set is 429/429, and collection is +3 against main. #1384 (truncate): its arms belong in 🤖 Generated with Claude Code |
|
@OffgridwithJD, a heads-up for this PR: #1401 fixes #1384 (truncate checks ownership before its lock) and adds three checks to Separately, while writing it I noticed that this suite's owner controls count 🤖 Generated with Claude Code |
…ueued-lock arm is watched while a refused probe runs, the holder holds until released, and four product checks lose the premise prefix (commandprompt#1341) - "the refused caller left no lock request queued" ran after every probe had exited, so it could not fail. It now polls pg_locks while a refused vacuum probe runs, in both twins, renamed to say so. - The shell holder slept a fixed 30 s; under a mutant that queues every probe, the eighth ran uncontended (measured: compact's arm stayed green on main). It now holds until released by a tag on its statement, and a premise asserts the lock is still held after the last probe. - The advance-hook owner call and vm's three columnar fixture checks read the code under test, so they are arms; the shell's owner call expects "ok", not an empty SQLSTATE. - The debug twin's gated-table read reports -1 when unreadable instead of raising. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho
…xit status, not from an absent ERROR line (commandprompt#1381) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho
|
Thanks for the #1401 heads-up; that order suits me. I took your owner-control point: now 9bc40f1, rebased on main.
🤖 Generated with Claude Code |
e697326 to
9bc40f1
Compare
jdatcmd
left a comment
There was a problem hiding this comment.
Reproduced at 9bc40f1 on PG18. vacuum_lock_privilege is 19/19. With vacuum's owner check moved after table_open, it's 17 + 2: the lock-order arm (got [locktimeout]) and now also the refused caller never queued a lock request on victim, watched while it ran: got [1] want [0]. That's the arm that couldn't fail before. Sorry I missed your 15:13 reply: all four points were in there, and the ran_ok helper closes the owner-control hole. Approving. I'll merge once CI is green, after checking the approval is on this head. Then #1401 rebases onto it.
🤖 Generated with Claude Code
…hook_privilege, seeded, as reviewed at d162def and restated on main (commandprompt#1341) The six commits jdatcmd reviewed at d162def, rebuilt on f601281 as one: the three twins, vm_privilege's owner controls on a table of their own, and the ledger seed restated from main (2460 + 58 = 2518, never 1944, ceiling 244 -> 241). TESTS.md sections renumbered 106-108 after main's 105. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho
…ueued-lock arm is watched while a refused probe runs, the holder holds until released, and four product checks lose the premise prefix (commandprompt#1341) - "the refused caller left no lock request queued" ran after every probe had exited, so it could not fail. It now polls pg_locks while a refused vacuum probe runs, in both twins, renamed to say so. - The shell holder slept a fixed 30 s; under a mutant that queues every probe, the eighth ran uncontended (measured: compact's arm stayed green on main). It now holds until released by a tag on its statement, and a premise asserts the lock is still held after the last probe. - The advance-hook owner call and vm's three columnar fixture checks read the code under test, so they are arms; the shell's owner call expects "ok", not an empty SQLSTATE. - The debug twin's gated-table read reports -1 when unreadable instead of raising. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho
…xit status, not from an absent ERROR line (commandprompt#1381) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho
9bc40f1 to
3ac2821
Compare
|
@jdatcmd Restated onto c6fecb2 (after #1400): now 3ac2821, rebased from 9bc40f1 which you approved.
After this one, #1404 is next. It depends on this one. 🤖 Generated with Claude Code |
jdatcmd
left a comment
There was a problem hiding this comment.
Verified 3ac2821 against my approval of 9bc40f1. It sits on current main (c6fecb2). The patch is identical outside the count files except the three TESTS.md numbers (now §112–114), and no main row changes (2744 / 2086, ceiling 234). I'll merge once CI is green. #1401 then rebases onto it and adds its three checks to your twin, and #1404 follows.
🤖 Generated with Claude Code
Part of #1341, the High twin-parity item "maintenance and debug entry points check ownership BEFORE taking a lock, and refuse non-owners".
vacuum_lock_privilege.sh,vm_privilege.shanddebug_hook_privilege.shhad no pytest twins. Each has one now, carrying every shell check by name, added toCOMPLETE. One shell fix came out of the porting.The twins
test_debug_hook_privilege.py: the two debug metadata mutators are bound as the suites bind them and probed by a non-owner on its own login. Each refusal is 42501 and "must be owner". The mirrors show the gate is ownership. It uses a private database, because the last mirror leaves a table that can't be dropped, so a per-test schema would fail its own teardown.test_vacuum_lock_privilege.py: a connection of the test's own holds ACCESS EXCLUSIVE onvictimwhile the non-owner calls eight entry points underlock_timeout 4s. Each is classified by SQLSTATE: 42501 is refused by ownership first, and 55P03 means it queued for the lock. The role holds USAGE on the test's schema, so no 42501 can be a schema refusal.test_vm_privilege.py: both refusal layers are probed with SET ROLE, as in the shell: the REVOKE stops schema-USAGE-only, and the C check stops EXECUTE-granted. The damage is measured too: no bit set, and the index-only scan agrees with a sequential scan. There is the heap refusal (fix(visibilitymap): enforce columnar relation check and pre-lock ownership #748) and sweep, and the install-script coverage scan, looped as the shell loops it.The shell fix (
vm_privilege.sh, both twins)The owner controls ran on
vmvictim. Under an attack that got through, block 0's bit was already set, so "the owner can still call vm_selftest" reddened for the attack, not for the owner. The ledger tool refused to date it as a kill, rightly. Both controls now run on a table of their own (vmown).Twin proof
PG17, main 4027b8d against this branch. Each pytest twin goes red at the same first check as its shell suite:
compactchecks ownership after taking its lock (the #749 bug)vm_selftestwithout its owner checkEvidence
cluster_tests534 → 537. Other open PRs take section 105 and move the counts, so whichever lands later renumbers.🤖 Generated with Claude Code