iommu: per-device domains with interim DMA-pool enforcement

Replaces L1's shared blanket identity domain with a private translation
domain per claimed PCI function. A device now reaches only:
  - the DMA pool: every dma_alloc'd region, mapped into every claimed
    device's domain (poolAdd/poolRemove, driven from the dma_alloc and
    dma_free syscalls). This keeps the cross-process buffer handoff
    working (fat's bounce buffer reaches the xHC) while blocking the
    kernel, page tables, process heaps, MMIO, and unallocated RAM.
  - its own firmware reserved region (RMRR), seeded at confine time.
The pool is the honest interim: devices can still reach one another's
DMA buffers. The DMA-region capability layer (next) narrows it to
per-grant reachability.

dma_free unmaps from every domain and invalidates BEFORE the frames
return to the allocator, closing the stale-IOTLB use-after-free window.
Driver death tears down its domains (detach + free tables) before the
broker claims and DMA frames are released.

New iommu_fault_drain syscall (+ driver.iommuFaultDrain) forces pending
fault records to the log on demand. The new iommu-fault case proves it:
a claimed e1000e is programmed to DMA-fetch its TX ring from an unmapped
page; VT-d faults the access (bdf 00:03.0 addr 0x1000 reason 0x6) and the
system stays alive. 104/104.
This commit is contained in:
Daniel Samson
2026-07-26 18:31:27 +01:00
parent f477ef7d9f
commit e94adcfc02
8 changed files with 280 additions and 43 deletions
+35
View File
@@ -215,6 +215,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
pciScanTest(boot_information);
} else if (eql(case, "pci-caps")) {
pciCapsTest(boot_information);
} else if (eql(case, "iommu-fault")) {
iommuFaultTest(boot_information);
} else if (eql(case, "acpi-parse")) {
acpiParseTest(boot_information);
} else if (eql(case, "acpi-report")) {
@@ -2453,6 +2455,39 @@ fn pciCapsTest(boot_information: *const BootInformation) void {
result();
}
/// IOMMU enforcement, the negative proof: boot with VT-d on and an unclaimed e1000e.
/// The manager spawns pci-bus, the fixture claims the NIC and fires a DMA at an
/// unmapped page; the unit must fault it and the system survive. Substance is asserted
/// by the harness on the kernel's DANOS-IOMMU-FAULT line and the fixture's markers.
fn iommuFaultTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: iommu-fault\n", .{});
if (boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over an initial_ramdisk", false);
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const rd = initial_ramdisk.Reader.init(image) orelse {
check("initial_ramdisk image is valid", false);
result();
return;
};
check("IOMMU enabled for the enforcement test", iommu.enabled());
process.setInitialRamdisk(image);
var manager: u32 = 0;
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
manager = process.spawnProcessSupervised(item.blob, 4, &.{"device-manager"}, scheduler.currentId(), null) catch 0;
break;
}
check("device-manager spawned", manager != 0);
check("iommu-fault-test spawned", spawnNamed(rd, "iommu-fault-test"));
result();
}
/// M19.1: the ring-3 PCI scan agrees with the kernel's. The manager spawns
/// pci-bus for the host bridge; the driver walks the same ECAM window through
/// its mmio_map grant and must find exactly the functions the kernel's own