DF-5518 / gpo_detach.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 | /* * gpo_detach.c -- DF-5518 race trigger: detach (and delete) the live * gpio_intel/gpio_acpi device pair while AML is concurrently executing * GeneralPurposeIo field accesses (TZ0._TMP hammer, driven by the acpi_tz * poll loop: each _TMP performs @ITER@ 59-pin GPIO field reads through * gpio_acpi_space_handler). * * device_detach(gpio_intel0) * -> bus_generic_detach -> gpio_acpi_detach (gpio_acpi.c:486-497) * -> gpio_acpi_unmap_aei() [AEI half: known DF-1094] * -> AcpiRemoveAddressSpaceHandler() [does NOT wait for the * in-flight gpio_acpi_space_handler dispatch; ACPICA runs the * handler with the interpreter EXITED (evregion.c:392-401), so * the removal proceeds concurrently and frees the HandlerObj * -- including the ContextMutex the running handler holds] * -> parent teardown: bus_teardown_intr, mem_res released, lockuninit * device_delete_children + device_delete_child * -> gpio_acpi_softc (with sc->space_handler_data == the space handler's * HandlerContext) and gpio_intel_softc are FREED while the handler may * still be running -> UAF. * * kfree'd-but-unreused memory still looks intact, so a naive first-touch * UAF is silent. To make the use deterministic we immediately reclaim the * freed zones with poisoned allocations: any in-flight handler deref * (GPIO_READ_PIN -> device_get_softc -> sc->ranges / sc->lk) then touches * 0xDE.. garbage -> fatal trap. * * Load with `kldload ./gpo_detach.ko`. */ #include <sys/param.h> #include <sys/module.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/bus.h> #include <sys/param.h> #include <vm/vm_page.h> static int gpo_detach_modevent(module_t mod, int type, void *arg) { devclass_t dc; device_t dev, parent; void *p[256]; volatile uint32_t *dbg1 = (volatile uint32_t *)PHYS_TO_DMAP(0xD6004); uint32_t d1a, d1b, d1c; int error, i, j, n; static const int sizes[] = { 128, 256, 512, 1024, 2048 }; switch (type) { case MOD_LOAD: dc = devclass_find("gpio_intel"); if (dc == NULL) { kprintf("gpo_detach: no gpio_intel devclass\n"); return (ENXIO); } dev = devclass_get_device(dc, 0); if (dev == NULL) { kprintf("gpo_detach: no gpio_intel0 device\n"); return (ENXIO); } kprintf("gpo_detach: racing %s...\n", device_get_nameunit(dev)); d1a = *dbg1; error = device_detach(dev); d1b = *dbg1; if (error == 0) { /* * NO console output between here and the poison * spray: serial kprintf costs ~100us/line and eats * the entire use-after-free window. */ device_delete_children(dev); parent = device_get_parent(dev); device_delete_child(parent, dev); /* * Reclaim the just-freed zones (softcs ~128/2048, * device_t ~256) with poisoned content so an * in-flight gpio_acpi_space_handler faults * deterministically instead of reading stale bytes. */ n = 0; for (i = 0; i < (int)NELEM(sizes); i++) { for (j = 0; j < 32 && n < 256; j++, n++) { p[n] = kmalloc(sizes[i], M_DEVBUF, M_WAITOK); if (p[n] != NULL) memset(p[n], 0xDE, sizes[i]); } } d1c = *dbg1; /* deliberately leak p[]; we want the window covered */ kprintf("gpo_detach: done, sprayed %d poison allocs; " "hammer dbg1 pre=%u post-detach=%u post-spray=%u\n", n, d1a, d1b, d1c); } break; case MOD_UNLOAD: break; default: return (EOPNOTSUPP); } return (0); } static moduledata_t gpo_detach_mod = { "gpo_detach", gpo_detach_modevent, NULL }; DECLARE_MODULE(gpo_detach, gpo_detach_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(gpo_detach, 1); |