//! iommu-fault-test — the negative proof for IOMMU enforcement. The iommu-fault QEMU //! case boots with VT-d enabled and an extra e1000e NIC no danos driver claims; this //! fixture claims it, then deliberately programs its transmit engine to DMA from a //! physical address that was never dma_alloc'd (so it is in no device's domain). The //! IOMMU must fault that access — the descriptor fetch never reaches memory — and the //! system must stay alive. A kernel `DANOS-IOMMU-FAULT` line plus this fixture's //! `system alive` marker is the pass. //! //! The rogue target is the e1000e's transmit descriptor RING base itself: the very first //! DMA the engine issues on a doorbell write is the descriptor fetch from that base, so //! pointing the ring at an unmapped page makes the first access the faulting one — no //! valid descriptor need be crafted. const std = @import("std"); const device = @import("driver"); const time = @import("time"); const logging = @import("logging"); const mmio = @import("mmio"); const pci = @import("pci"); const pci_class = @import("pci-class"); const intel_vendor: u16 = 0x8086; const e1000e_device: u16 = 0x10D3; const ethernet_class: u64 = pci_class.ClassCode.pack(.{ .base = @intFromEnum(pci_class.BaseClass.network), .subclass = @intFromEnum(pci_class.network.SubClass.ethernet), .prog_if = 0, }); // e1000e transmit-engine registers (Intel 82574L datasheet §Register Descriptions), // byte offsets within BAR0. VERIFY-AGAINST-SPEC held on first bring-up: these are the // legacy TX ring registers. const reg_tctl = 0x0400; // Transmit Control const reg_tdbal = 0x3800; // TX Descriptor Base Address Low const reg_tdbah = 0x3804; // TX Descriptor Base Address High const reg_tdlen = 0x3808; // TX Descriptor Length (bytes, 128-byte aligned) const reg_tdh = 0x3810; // TX Descriptor Head const reg_tdt = 0x3818; // TX Descriptor Tail const tctl_en: u32 = 1 << 1; // Transmit Enable const tctl_psp: u32 = 1 << 3; // Pad Short Packets /// A low physical page that user DMA never touches — never returned by dma_alloc (whose /// arena is far higher), so it is in no device's IOMMU domain. The e1000e's descriptor /// fetch from here is exactly the out-of-domain access the unit must block. const rogue_physical: u64 = 0x1000; var descriptor: device.DeviceDescriptor = undefined; fn writeLine(comptime fmt: []const u8, arguments: anytype) void { var line: [128]u8 = undefined; _ = logging.write(std.fmt.bufPrint(&line, fmt, arguments) catch return); } pub fn main() void { const nic_id: u64 = found: { var tries: u32 = 0; while (tries < 150) : (tries += 1) { var descriptors: [64]device.DeviceDescriptor = undefined; const total = device.enumerate(&descriptors); for (descriptors[0..@min(total, descriptors.len)]) |*entry| { if (entry.class == @intFromEnum(device.DeviceClass.pci_device) and entry.pci_class == ethernet_class) { descriptor = entry.*; break :found entry.id; } } time.sleepMillis(100); } _ = logging.write("iommu-fault-test: FAIL no ethernet function found\n"); return; }; if (!device.claim(nic_id)) { _ = logging.write("iommu-fault-test: FAIL claim\n"); return; } var function = pci.Function.map(nic_id, &descriptor) orelse { _ = logging.write("iommu-fault-test: FAIL config-space map\n"); return; }; if (function.vendorId() != intel_vendor or function.deviceId() != e1000e_device) { _ = logging.write("iommu-fault-test: FAIL not an e1000e\n"); return; } function.enableMemoryAndBusMaster(); const bar0 = function.mapBar(0) orelse { _ = logging.write("iommu-fault-test: FAIL map BAR0\n"); return; }; // Point the TX ring at the rogue page and kick the engine: TDBA = rogue, a non-zero // length, head=0, enable, then tail=1 so the engine fetches descriptor 0 — a DMA // read from the rogue page, which the IOMMU must fault. _ = logging.write("iommu-fault-test: pointing e1000e TX ring at an unmapped page\n"); mmio.writeRegister(u32, bar0 + reg_tdbal, @truncate(rogue_physical)); mmio.writeRegister(u32, bar0 + reg_tdbah, @intCast(rogue_physical >> 32)); mmio.writeRegister(u32, bar0 + reg_tdlen, 128); mmio.writeRegister(u32, bar0 + reg_tdh, 0); mmio.writeRegister(u32, bar0 + reg_tctl, tctl_en | tctl_psp); mmio.writeMemoryBarrier(); mmio.writeRegister(u32, bar0 + reg_tdt, 1); // doorbell: fetch descriptor 0 // Give the engine time to attempt the fetch, then force the fault records to the log. time.sleepMillis(200); const faults = device.iommuFaultDrain(); writeLine("iommu-fault-test: drained {d} iommu fault(s)\n", .{faults}); if (faults == 0) { _ = logging.write("iommu-fault-test: FAIL rogue DMA was not blocked\n"); return; } // Liveness: the system survived the blocked DMA — read our own config space back. if (function.vendorId() != intel_vendor) { _ = logging.write("iommu-fault-test: FAIL device unreadable after fault\n"); return; } _ = logging.write("iommu-fault-test: system alive\n"); }