DF-5464 / icu_msiabi_probe.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | /* * DF-5464 evidence (safe phase): drive the ICU MSI/MSI-X ABI entry points * directly as a driver (KLD) would. * * Safe phase (no crash expected on a production kernel): * - msix_alloc/msi_map/msix_release round trip (count=1, legal) * - msi_alloc(zone, 64, 0): 'zone' is a 128-int buffer; only zone[0..31] * are inside the ABI's documented count<=32 contract (icu_abi.c:460). * If words zone[32..63] come back modified, icu_abi_msi_alloc_intern() * wrote past the caller's 32-int array (icu_abi.c:498 'intrs[j] = intr'; * the reference caller's array is the on-stack 'int irqs[32]' at * sys/bus/pci/pci.c:1952/2072). * * On an INVARIANTS kernel this instead panics at the KASSERT * ("invalid count 64", icu_abi.c:460), proving the KASSERT is the only * guard. Stock GENERIC ships WITHOUT INVARIANTS (sys/config/GENERIC), * as does the audit guest's X86_64_NOINV baseline kernel. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/machintr.h> extern struct machintr_abi MachIntrABI_ICU; #define CANARY 0xAAAAAAA static int zone[128]; static void probe_msix(void) { uint64_t addr; uint32_t data; int irq = -1, rc; rc = MachIntrABI_ICU.msix_alloc(&irq, 0); kprintf("icu_msiabi_probe: msix_alloc(cpu0) rc=%d irq=%d\n", rc, irq); if (rc == 0) { MachIntrABI_ICU.msi_map(irq, &addr, &data, 0); kprintf("icu_msiabi_probe: msi_map addr=0x%llx data=0x%x\n", (unsigned long long)addr, data); MachIntrABI_ICU.msix_release(irq, 0); kprintf("icu_msiabi_probe: msix_release done\n"); } } static void probe_count64(void) { int i, hit, rc; for (i = 0; i < 128; ++i) zone[i] = CANARY; kprintf("icu_msiabi_probe: calling ICU msi_alloc(count=64, cpu0) " "(contract max is 32)\n"); rc = MachIntrABI_ICU.msi_alloc(zone, 64, 0); kprintf("icu_msiabi_probe: msi_alloc rc=%d\n", rc); if (rc != 0) { kprintf("icu_msiabi_probe: alloc failed, no canary test\n"); return; } kprintf("icu_msiabi_probe: zone[0..7] ="); for (i = 0; i < 8; ++i) kprintf(" %d", zone[i]); kprintf(" (allocated block)\n"); hit = 0; for (i = 32; i < 128; ++i) { if (zone[i] != CANARY) ++hit; } kprintf("icu_msiabi_probe: %d/96 canary words past zone[31] " "overwritten (32 expected for a 64-count alloc)\n", hit); for (i = 32; i < 64; ++i) { if (zone[i] != CANARY) { kprintf("icu_msiabi_probe: zone[%d]=%d <-- OOB " "write past 32-int contract\n", i, zone[i]); break; } } /* * Release the full 64-wide view so the maps are restored * (release_intern also reads intrs[0..63], icu_abi.c:533). */ MachIntrABI_ICU.msi_release(zone, 64, 0); kprintf("icu_msiabi_probe: msi_release(64) done\n"); } static int icu_msiabi_modevent(module_t mod __unused, int type, void *data __unused) { switch (type) { case MOD_LOAD: kprintf("icu_msiabi_probe: loaded\n"); probe_msix(); probe_count64(); kprintf("icu_msiabi_probe: SAFE PHASE COMPLETE\n"); return 0; case MOD_UNLOAD: return 0; default: return EOPNOTSUPP; } } static moduledata_t icu_msiabi_probe_mod = { "icu_msiabi_probe", icu_msiabi_modevent, NULL }; DECLARE_MODULE(icu_msiabi_probe, icu_msiabi_probe_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); MODULE_VERSION(icu_msiabi_probe, 1); |