DF-5042 / wrapper.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 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | /* * DF-5042 / DF-5043 PoC wrapper — replay the stock adv_pci_attach() * (verbatim sys/dev/disk/advansys/adv_pci.c, #included below) against a * scripted AdvanSys PCI chip, and observe what the attach error unwinds * destroy / release. * * Two scenarios (kenv-selected): * * default : REAL adv_init() is used. The fake chip window has * the AdvanSys signature planted and CHIP_STATUS * (offset 0x0E) stays 0, so adv_init() deterministi- * cally fails at its first halt check * ("Unable to halt adapter", advansys.c:794-798) * -> adv_pci.c:260-264 unwind runs. * * kenv df5042.irqfail: fk_adv_init() scripts success so attach proceeds * to the IRQ stage; fk_bus_setup_intr() fails * -> adv_pci.c:289-294 unwind runs. * * Everything else is the unmodified driver code: the real adv_find_signature * reads the planted signature, the real bus_dma_tag_create/bus_dmamem_alloc/ * bus_dmamap_load build the real parent_dmat and the shared overrun buffer, * and the real adv_free() executes the init_level switch. * * Expected baseline output (the findings): * mode 1: bus_dma_tag_destroy calls for parent_dmat == 0 *** LEAK (DF-5042) * mode 2: parent_dmat destroy == 0 AND bus_release_resource for the IRQ * resource == 0 *** LEAKS (DF-5042+5043) * With fix.diff applied to adv_pci.c the same replay shows * parent_dmat destroy == 1 and IRQ release == 1. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/rman.h> #include <sys/bus.h> #include <machine/bus_dma.h> #include <machine/pmap.h> #include <vm/pmap.h> #include <vm/vm_extern.h> #include <vm/vm_kern.h> #include "fkdecl.h" /* * Pull in <bus/pci/pcivar.h> BEFORE the renames below: pcivar.h defines * pci_read_config()/pci_write_config() as static __inline wrappers, which * would collide with our shim definitions if first seen under the renames. */ #include <bus/pci/pcireg.h> #include <bus/pci/pcivar.h> #define FAKE_BAR_PHYS 0x60000000UL /* real RAM on this guest (4GB) */ #define WIN_SIZE 0x1000 static struct resource fake_iores; static struct resource fake_irqres; static struct adv_softc fake_softc; static bus_space_tag_t ftag; static bus_space_handle_t fwin; static bus_dma_tag_t t_parent; /* captured from fk tag_create #1 */ static bus_dma_tag_t t_overrun; /* captured from fk tag_create #2 */ static int cnt_create; static int cnt_destroy_parent; static int cnt_destroy_overrun; static int cnt_rel_io; static int cnt_rel_irq; static int cnt_pci_write; static int cnt_setup_intr; static int mode_irqfail; /* ---------------- observation shims (all forward to the real thing) ----- */ u_int32_t fk_pci_read_config(device_t dev, int reg, int width) { switch (reg) { case PCIR_DEVVENDOR: return (0x110010CD); /* PCI_DEVICE_ID_ADVANSYS_1200A */ case PCIR_COMMAND: return (0); /* force the PORTEN/BUSMASTEREN write */ default: return (0); } } void fk_pci_write_config(device_t dev, int reg, uint32_t val, int width) { cnt_pci_write++; } struct resource * fk_bus_alloc_resource(device_t dev, int type, int *rid, u_long start, u_long end, u_long count, u_int flags) { if (type == SYS_RES_IOPORT) { fake_iores.r_bustag = ftag; fake_iores.r_bushandle = fwin; kprintf("FK: bus_alloc_resource(IOPORT) -> fake io resource " "(tag=%p h=%p)\n", (void *)(uintptr_t)ftag, (void *)fwin); return (&fake_iores); } if (type == SYS_RES_IRQ) { kprintf("FK: bus_alloc_resource(IRQ) -> fake irq resource %p\n", &fake_irqres); return (&fake_irqres); } return (NULL); } int fk_bus_release_resource(device_t dev, int type, int rid, struct resource *res) { if (res == &fake_iores) { cnt_rel_io++; kprintf("FK: bus_release_resource(IOPORT)\n"); } else if (res == &fake_irqres) { cnt_rel_irq++; kprintf("FK: bus_release_resource(IRQ)\n"); } return (0); } int fk_bus_setup_intr(device_t dev, struct resource *r, int flags, driver_intr_t handler, void *arg, void **cookiep, lwkt_serialize_t serializer) { cnt_setup_intr++; kprintf("FK: bus_setup_intr -> scripted %s\n", mode_irqfail ? "FAILURE" : "success"); return (mode_irqfail ? 1 : 0); } struct adv_softc * fk_adv_alloc(device_t dev, bus_space_tag_t tag, bus_space_handle_t bsh) { /* * adv_alloc() verbatim except the softc is our storage instead of * device_get_softc(dev) (dev is the real pci0 device, whose softc * belongs to the PCI bridge). */ bzero(&fake_softc, sizeof(fake_softc)); LIST_INIT(&fake_softc.pending_ccbs); SLIST_INIT(&fake_softc.free_ccb_infos); fake_softc.dev = dev; fake_softc.unit = 0; fake_softc.tag = tag; fake_softc.bsh = bsh; kprintf("FK: adv_alloc(dev=%p, tag=%p, h=%p) -> softc %p\n", dev, (void *)(uintptr_t)tag, (void *)bsh, &fake_softc); return (&fake_softc); } int fk_adv_init(struct adv_softc *adv) { if (mode_irqfail) { kprintf("FK: adv_init -> scripted SUCCESS (reaching IRQ " "stage)\n"); return (0); } kprintf("FK: adv_init -> REAL adv_init() against scripted chip\n"); return (adv_init(adv)); /* the real thing, advansys.c */ } int fk_find_signature(bus_space_tag_t tag, bus_space_handle_t bsh) { /* * Forced pass. The real adv_find_signature() reads byte 0x01 * (== 0x25) AND word 0x00 (== 0x04C1/0x00C1) — two reads of the * same two bytes that cannot both be satisfied by a passive RAM * window; real silicon latches the signature registers differently. * The signature check is not what this PoC tests, so it is forced * here and the code under test (dma setup + failure unwinds) runs * exactly as on a real card. */ kprintf("FK: adv_find_signature -> forced pass (RAM window cannot " "emulate the dual-width signature latch)\n"); return (1); } int fk_bus_dma_tag_create(bus_dma_tag_t parent, bus_size_t alignment, bus_size_t boundary, bus_addr_t lowaddr, bus_addr_t highaddr, bus_size_t maxsize, int nsegments, bus_size_t maxsegsz, int flags, bus_dma_tag_t *dmat) { int error = bus_dma_tag_create(parent, alignment, boundary, lowaddr, highaddr, maxsize, nsegments, maxsegsz, flags, dmat); if (error == 0) { cnt_create++; if (t_parent == NULL) t_parent = *dmat; else if (t_overrun == NULL) t_overrun = *dmat; kprintf("FK: bus_dma_tag_create #%d -> tag %p\n", cnt_create, *dmat); } return (error); } int fk_bus_dma_tag_destroy(bus_dma_tag_t dmat) { if (dmat != NULL && dmat == t_parent) { cnt_destroy_parent++; kprintf("FK: bus_dma_tag_destroy(PARENT dmat %p) #%d\n", dmat, cnt_destroy_parent); } else if (dmat != NULL && dmat == t_overrun) { cnt_destroy_overrun++; kprintf("FK: bus_dma_tag_destroy(OVERRUN dmat %p) #%d\n", dmat, cnt_destroy_overrun); } return (bus_dma_tag_destroy(dmat)); /* the real thing */ } /* ---- the code under test ---------------------------------------------- */ /* * adv_pci.c is included VERBATIM. The renames below (the equivalent of -D * command-line flags) sit AFTER our shim definitions above, so the shims * themselves call the real kernel functions while every call adv_pci.c * makes to these externals lands in the observation shims. pcivar.h was * already included above, so its static __inline pci_* wrappers are not * re-parsed under the renames. DRIVER_MODULE is neutered (the running * kernel already contains an identical pci/adv module from `device adv0`, * so re-registering would collide); the driver-table statics at the bottom * of adv_pci.c are kept referenced by the global pointers below. */ #define pci_read_config fk_pci_read_config #define pci_write_config fk_pci_write_config #define bus_alloc_resource fk_bus_alloc_resource #define bus_release_resource fk_bus_release_resource #define bus_setup_intr fk_bus_setup_intr #define adv_alloc fk_adv_alloc #define adv_init fk_adv_init #define adv_find_signature fk_find_signature #define bus_dma_tag_create fk_bus_dma_tag_create #define bus_dma_tag_destroy fk_bus_dma_tag_destroy #undef DRIVER_MODULE #define DRIVER_MODULE(a, b, c, d, e, f) /* neutered: kernel has pci/adv */ #include "adv_pci.c" /* VERBATIM sys/dev/disk/advansys/adv_pci.c */ /* keep adv_pci.c's neutered statics referenced (no -Wunused-variable) */ driver_t *df_ref_driver = &adv_pci_driver; devclass_t *df_ref_devclass = &adv_pci_devclass; /* ------------------------------ driver --------------------------------- */ static int df5042_modevent(module_t mod, int type, void *arg) { device_t dev; int attach_ret; int leaked_parent, leaked_irq; switch (type) { case MOD_LOAD: mode_irqfail = kgetenv("df5042.irqfail") != NULL; fwin = (bus_space_handle_t)pmap_mapdev_uncacheable( FAKE_BAR_PHYS, WIN_SIZE); ftag = (bus_space_tag_t)X86_64_BUS_SPACE_MEM; /* * Plant the AdvanSys signature (adv_find_signature reads * byte 0x01 == 0x25 and word 0x00 == 0x04C1) and leave * CHIP_STATUS (0x0E) at 0 so the real adv_init() fails its * halt check deterministically. */ ((volatile u_int8_t *)fwin)[0] = 0xC1; ((volatile u_int8_t *)fwin)[1] = 0x25; dev = devclass_get_device(devclass_find("pci"), 0); if (dev == NULL) dev = devclass_get_device(devclass_find("nexus"), 0); kprintf("DF5042: mode=%s, chip window=%p, dev=%p\n", mode_irqfail ? "IRQFAIL" : "INITFAIL", (void *)fwin, dev); attach_ret = adv_pci_attach(dev); /* code under test */ kprintf("DF5042: adv_pci_attach returned %d\n", attach_ret); kprintf("DF5042: pci_write_config=%d dma tags created=%d " "(parent=%p overrun=%p)\n", cnt_pci_write, cnt_create, t_parent, t_overrun); kprintf("DF5042: bus_dma_tag_destroy: parent=%d overrun=%d\n", cnt_destroy_parent, cnt_destroy_overrun); kprintf("DF5042: bus_release_resource: IOPORT=%d IRQ=%d; " "bus_setup_intr calls=%d\n", cnt_rel_io, cnt_rel_irq, cnt_setup_intr); leaked_parent = (t_parent != NULL && cnt_destroy_parent == 0); leaked_irq = (mode_irqfail && cnt_rel_irq == 0); if (leaked_parent) kprintf("DF5042: *** parent_dmat NEVER DESTROYED on " "the failure unwind (adv_free case 1 tears down " "from the ccb_infos tier) -- DF-5042 LEAK " "PROVEN ***\n"); if (leaked_irq) kprintf("DF5042: *** IRQ resource NEVER RELEASED on " "the bus_setup_intr failure unwind -- DF-5043 " "LEAK PROVEN ***\n"); if (!leaked_parent && !leaked_irq) kprintf("DF5042: unwind clean (fix effective?)\n"); /* keep the guest clean: manually undo everything leaked */ if (leaked_parent) bus_dma_tag_destroy(t_parent); if (t_overrun != NULL && overrun_buf != NULL) { bus_dmamap_unload(t_overrun, overrun_dmamap); bus_dmamem_free(t_overrun, overrun_buf, overrun_dmamap); bus_dma_tag_destroy(t_overrun); overrun_buf = NULL; overrun_dmat = NULL; overrun_physbase = 0; } else if (t_overrun != NULL && cnt_destroy_overrun == 0) { bus_dma_tag_destroy(t_overrun); } kprintf("DF5042: cleanup done, guest still healthy\n"); return (0); case MOD_UNLOAD: kprintf("DF5042: unloaded\n"); return (0); default: return (EOPNOTSUPP); } } static moduledata_t df5042_mod = { "df5042", df5042_modevent, NULL }; DECLARE_MODULE(df5042, df5042_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |