kernel: the give paths take the lock, and a give confines afresh after a death
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.
This commit is contained in:
@@ -477,11 +477,19 @@ pub fn transferErrnoOf(e: TransferError) i64 {
|
||||
/// Note this is deliberately NOT the M13 capability-passing path, which shares a handle
|
||||
/// refcounted — a copy. Exclusivity cannot be expressed that way.
|
||||
pub fn transfer(id: u64, from: u32, to: u32) TransferError!void {
|
||||
try canTransfer(id, from);
|
||||
claimed[@intCast(id)] = to;
|
||||
giver[@intCast(id)] = from;
|
||||
}
|
||||
|
||||
/// The checks `transfer` will make, without the move. The syscall layer runs them
|
||||
/// first — under the same lock hold that the transfer itself will run under — so it
|
||||
/// can refuse, or arrange the IOMMU confinement the move needs, while nothing has
|
||||
/// mutated yet and there is nothing to roll back.
|
||||
pub fn canTransfer(id: u64, from: u32) TransferError!void {
|
||||
if (id >= count) return error.NoSuchDevice;
|
||||
const holder = claimed[@intCast(id)] orelse return error.NotHeld;
|
||||
if (holder != from) return error.NotHeld;
|
||||
claimed[@intCast(id)] = to;
|
||||
giver[@intCast(id)] = from;
|
||||
}
|
||||
|
||||
/// The errno a refused `claim` returns to ring 3. (`ECONFINE` — the claim stood but
|
||||
|
||||
Reference in New Issue
Block a user