Three holes from the real-AMD audit, one shared root: the delegation flag-day
added paths that touch the broker table and the IOMMU records without the big
kernel lock, and a loan-return rule whose re-delegation skipped confinement.
- device_enumerate walked the table with no lock. The table stopped being a
static array in the bounds track — reserve() regrows it through realloc on
every boot — so an unlocked reader can be mid-copy out of a slice that
device_register on another core has already freed and reused, or pair a fresh
count with a stale slice. Each chunk is now snapshotted under the lock; the
copy to the user stays outside it.
- system_spawn's give ran entirely unlocked — the comment claiming "the lock
has not been dropped" was false (spawnProcessSupervised takes and releases it
internally). The ownership pre-check now only spares creating a doomed child;
the give itself re-checks, confines and moves in one lock hold, and a give
that fails after the spawn kills the child rather than leaving it running
without the hardware it was spawned for.
- A re-delegated device after a driver death was never re-confined. Death tears
the domain down before the loan returns to the lender, so the next give found
no active record, reassign no-op'd, and the respawned driver ran the device
with a V=0 device-table entry and no domain — silently unconfined, the exact
fail-open the fail-closed claim was built to remove. Both give paths now share
one body (giveDeviceLocked): check first, confine afresh when no record is
active — refusing with ECONFINE like the claim — and move last, when nothing
can fail.
The iommu test drives the death-and-respawn sequence directly; with the old
reassign-only behaviour its two confinement checks fail, with this change the
suite is 118/118.