DF-5450 / nxmap.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 | /* * DF-5450 PoC — nexus_activate_resource() is not idempotent. * * sys/platform/pc64/x86_64/nexus.c:420-451 maps a SYS_RES_MEMORY resource * with pmap_mapdev() BEFORE calling the (idempotent) rman_activate_resource() * and overwrites r_virtual with the new mapping. Activating an already * RF_ACTIVE resource therefore allocates a SECOND kernel_map range and fresh * RW PTEs for the same physical MMIO window; the first range is orphaned * forever (its only handle, r_virtual, is overwritten). On failure of * rman_activate_resource() the fresh mapping is likewise never unmapped. * * This module (a nexus child driver, the API surface nexus exposes): * 1. allocates + activates a 1-page MMIO resource -> vaddr1 * 2. calls the documented bus_activate_resource() API again on the same * active resource -> vaddr2 != vaddr1 * 3. releases the resource (unmaps ONLY vaddr2) * 4. reads through vaddr1 afterwards — still mapped (stale RW PTE) and * kernel_map->size stays elevated by one page => leaked KVA+PTEs. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/bus.h> #include <sys/rman.h> #include <sys/systm.h> #include <vm/vm_kern.h> static int nxmap_probe(device_t); static int nxmap_attach(device_t); static void nxmap_identify(driver_t *, device_t); static device_method_t nxmap_methods[] = { DEVMETHOD(device_identify, nxmap_identify), DEVMETHOD(device_probe, nxmap_probe), DEVMETHOD(device_attach, nxmap_attach), DEVMETHOD_END }; static driver_t nxmap_driver = { "nxmap", nxmap_methods, 0 }; static devclass_t nxmap_devclass; DRIVER_MODULE(nxmap, nexus, nxmap_driver, nxmap_devclass, NULL, NULL); /* physical MMIO window in the QEMU PCI hole; PTE-backed read is safe */ #define POC_PA 0xE0000000UL #define POC_SZ 0x1000UL static void nxmap_identify(driver_t *drv, device_t parent) { if (device_find_child(parent, "nxmap", 0) == NULL) BUS_ADD_CHILD(parent, parent, 0, "nxmap", 0); } static int nxmap_probe(device_t dev) { device_set_desc(dev, "nexus activate idempotence PoC"); return (0); } static int nxmap_attach(device_t dev) { struct resource *r; vm_offset_t v1, v2, first_v1; uint32_t d1, d2, d1_after; const char *stale; int rid, err, i; vm_offset_t ksize0, ksize1, ksize2; rid = 100; if (bus_set_resource(dev, SYS_RES_MEMORY, rid, POC_PA, POC_SZ, -1)) { kprintf("nxmap: set_resource failed\n"); return (ENXIO); } ksize0 = kernel_map->size; /* * Cycle alloc -> activate -> release 16 times: each cycle maps the * MMIO window twice but unmaps only the second mapping, so one page * of kernel_map KVA (+ its RW PTEs) is stranded per cycle. */ for (i = 0; i < 16; i++) { r = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (r == NULL) { kprintf("nxmap: alloc failed at cycle %d\n", i); return (ENXIO); } v1 = (vm_offset_t)rman_get_virtual(r); if (i == 0) { d1 = *(volatile uint32_t *)v1; first_v1 = v1; } err = bus_activate_resource(dev, SYS_RES_MEMORY, rid, r); v2 = (vm_offset_t)rman_get_virtual(r); d2 = *(volatile uint32_t *)v2; if (i == 0) { kprintf("DF-5450: err2ndact=%d vaddr1=%p vaddr2=%p " "same_phys=%s\n", err, (void *)v1, (void *)v2, d1 == d2 ? "YES" : "no"); } bus_release_resource(dev, SYS_RES_MEMORY, rid, r); } v1 = first_v1; ksize1 = kernel_map->size; ksize2 = kernel_map->size; /* * The orphaned first mapping: still alive after full release. * On a fixed (idempotent) nexus, v1 == v2 was properly unmapped by * the release, so dereferencing would fault -- guard the probe. */ if (v1 != v2) { d1_after = *(volatile uint32_t *)v1; stale = d1_after == d1 ? "YES" : "no"; } else { d1_after = 0; stale = "N/A (v1 == v2, single mapping)"; } kprintf("DF-5450: err2ndact=%d vaddr1=%p vaddr2=%p same_phys=%s\n", err, (void *)v1, (void *)v2, d1 == d2 ? "YES" : "no"); kprintf("DF-5450: kernel_map size before=%ju after2nd=%ju " "afterrelease=%ju (leaked pages: %ju)\n", (uintmax_t)ksize0, (uintmax_t)ksize1, (uintmax_t)ksize2, (uintmax_t)((ksize2 - ksize0) / PAGE_SIZE)); kprintf("DF-5450: orphan vaddr1 read after full release: " "%#x (stale RW PTE still present: %s)\n", d1_after, stale); return (0); } |