F-LEAK third attempt: band-scoped fenced destroy (v0.110.0)

The per-VM ACL is consumed by the destroy it authorises (PVE remove_vm_access,
LXC.pm:906), so it works once per slot. Fourth root-fenced exception, band-enforced in
sudoers literally + in code + at the caller. API destroy still tried first.
This commit is contained in:
2026-07-28 11:28:54 +02:00
parent ff7f68e089
commit 50751b8901
5 changed files with 243 additions and 10 deletions
+37
View File
@@ -1,3 +1,40 @@
## v0.110.0 — F-LEAK, third attempt: the fourth root-fenced exception (2026-07-28)
**The band-scoped ACL fix (v1.21.0) is durable for exactly ONE use per slot, and the live check caught
it.** After the first successful teardown of `990000`, `pveum acl list` showed **0 grants** at
`/vms/990000`. Cause, confirmed in PVE's own source rather than inferred: the destroy path calls
`PVE::AccessControl::remove_vm_access($vmid)` (`API2/LXC.pm:906`), which deletes
`acl_root->children->vms->children->{$vmid}`**every ACL at `/vms/<vmid>`** (`AccessControl.pm:1898`).
**The grant is consumed by the very operation it authorises.** After ten teardowns the whole band is
ungranted and F-LEAK returns. A per-VM ACL is structurally the wrong tool here; the pool grant is
durable only because it lives on a *pool* path, which `remove_vm_access` does not touch (it removes the
guest's pool *membership*, not the pool's ACL).
**So this is attempt three, and it is the route the task offered as the alternative:** route the destroy
through the same authority that already performs the create. `Privileged.DestroyScratchLXC` is the
**fourth** root-fenced exception (previously exactly three: keyctl `pct create`, USB mount/fstab,
SMART/sensors), and it is fenced harder than any of them:
| layer | enforcement |
|---|---|
| **sudoers** | `/usr/sbin/pct destroy 99000[0-9] --purge` — sudo matches the vmid **literally**. Even a compromised agent asking for `pct destroy 9201` is refused by sudo itself. |
| **`DestroyScratchLXC`** | re-checks the band before exec; refuses an unconfigured or inverted band rather than defaulting |
| **`teardownScratch`** | acts only on this journal entry's own scratch provenance |
None of the three is consumed by use, which is the property the ACL lacked. The API destroy is still
tried **first** and remains the normal path; this is the fallback that makes teardown deterministic
instead of once-per-slot. The v1.21.0 band ACLs stay provisioned — they make the common case need no
privileged call at all.
**Ships with a sudoers change**, so `configs/felhom-agent.sudoers` must be deployed with the binary
(`visudo -cf`: parsed OK).
Red-proofs 9 and 10, both observed failing: removing the band check →
`REFUSAL FAILED: executed [pct destroy 9201 --purge] for out-of-band vmid 9201`; removing the
unconfigured-band check → `an unconfigured band admitted vmid 0 and EXECUTED [pct destroy 0 --purge]`.
The out-of-band table deliberately includes **9201**, the live customer guest on both demo boxes.
## v0.109.0 — the guest-power watchdog gets the observable it was shipped without (2026-07-28)
**Self-correction to v0.107.0, found by running the very check this session added elsewhere.** The new