merge: a checker for the deprovision spec, and the trap that makes it pass for free

This commit is contained in:
2026-08-12 04:11:38 +00:00
2 changed files with 91 additions and 2 deletions
+22 -2
View File
@@ -146,8 +146,28 @@ All of these must hold for the freed uid *and* its freed subuid range:
- `/run/user/<uid>` absent
- no processes owned by the uid
Worth extracting as `assertUidFree(uid, subuidRange)` and reusing it as the post-condition of the function and
as a test.
`scripts/assert-uid-free.sh` implements exactly this, deliberately **outside** the function: a checker the
implementation calls is a restatement of its own beliefs, not an audit. Two modes, and the split matters —
```
./scripts/assert-uid-free.sh --capture green # BEFORE: prints "green 1001 165536 65536"
sudo ./scripts/assert-uid-free.sh --check green 1001 165536 65536 # AFTER: exit 1 unless clean
```
The range has to be captured **before** deletion, because `userdel` removes the `/etc/subuid` entry with the
account. After that there is no way to ask what range it held — and a check that silently skips that half is
the exact failure this section exists to prevent.
**The subuid check passes vacuously on most accounts, and that is a trap.** Container files are owned by a
mapped id only when a process inside the container runs as a NON-root user; an image whose files are root-owned
maps to the member's own uid and leaves nothing in the range. Measured on green after a night of real use —
`claude` installed, images pulled, transcripts written — the range check found **zero** files and passed
without testing anything.
To build a specimen that actually exercises it, run a container whose process writes as a non-root user. The
`postgres:18-alpine` case from the same night is the natural one: its entrypoint drops to uid 70, and the data
directory came out owned by `subuid_start + 70` on the host. Verify the range check *fails* on that tree before
trusting it to pass on a cleaned one.
**Trap for the verifier:** do not use `sudo -u <user> …` to check anything after step 2. Creating a session
starts a user manager and recreates `/run/user/<uid>`, so the check would undo the step it is verifying.