bus_dmamap_unload() of an EINPROGRESS-queued map is a silent no-op: phantom deferred load + callback on stale buffer state, reservation leak
Summary
_bus_dmamap_unload only frees active bounce pages - a queued map stays on the waiting list with stale buf/buflen/callback and partial reservation; any later bounce release in the zone dequeues the UNLOADED map and busdma_swi runs a full deferred load on it: driver callback for an aborted transaction over the freed/reused buffer (pmap_kextract of stale vaddr + bounce copies in sync = write-after-free at the driver) + stuck reservations drain the shared zone. Guest-proven (harness POC_TEST=2): 'DEFERRED CALLBACK FIRED FOR UNLOADED MAP', PHANTOM CONFIRMED; patched kernel FAILED (no callback). Same diff as DF-5347.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5348 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| busdmauaf.c | β | 12.4 KB | view raw | |
| run.log.baseline | β | 4.7 KB | β download | |
| VERDICT.md | β | 2.8 KB | β raw | |
| README.md | β | 2.0 KB | β raw | |
| fix.diff | β | 2.3 KB | view raw | |
| build.log | β | 800 B | view raw | |
| env.txt | β | 1.2 KB | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| verdict.json | β | 3.3 KB | view raw |
DF-5348 β bus_dmamap_unload() of a deferred-load (EINPROGRESS) map is a silent no-op β phantom deferred load + callback on stale state
sys/platform/pc64/x86_64/busdma_machdep.c (DragonFly 6.5-DEVELOPMENT)
What
bus_dmamap_unload (macro, sys/sys/bus_dma.h:313) only calls
_bus_dmamap_unload() (busdma_machdep.c:1022), which merely frees active
bounce pages. For a map queued at EINPROGRESS it is a complete no-op:
the map stays on bz->bounce_map_waitinglist, keeps pagesneeded/
pagesreserved and its partial bounce-page reservation, and keeps
stale buf/buflen/callback/callback_arg. Nothing ever dequeues it
except get_map_waiting().
When any bounce page is later freed anywhere in the zone, the reservation
completes, the map is dequeued, and busdma_swi() executes
bus_dmamap_load(map->dmat, map, map->buf, map->buflen, ...) β a full
deferred load for a map the driver has already unloaded β and invokes
the driver callback with stale state. If the driver freed or reused the
buffer after unload (the normal abort path), the deferred load
pmap_kextracts the stale buffer and later bus_dmamap_sync bounces
into/out of it β write-after-free at the driver level. The stuck
reservation also silently shrinks the zone (DoS).
Reproduce
cd /root/busdmauaf printf "KMOD=busdmauaf_t2\nSRCS=busdmauaf.c\nCOPTS+=-DPOC_TEST=2\n..." > Makefile make -m /usr/share/mk SYSDIR=/usr/src/sys MAKEOBJDIRPREFIX=/root/obj kldload ./busdmauaf_t2.ko dmesg | grep PHANTOM
Expected (baseline)
BUSDMA-PHANTOM: mapY load -> 36 (EINPROGRESS; mapY queued, ...) BUSDMA-PHANTOM: bus_dmamap_unload(mapY) returned -- map is STILL on the waiting list BUSDMA-PHANTOM: *** DEFERRED CALLBACK FIRED FOR UNLOADED MAP *** (after unload(mapX)) BUSDMA-PHANTOM: RESULT=PHANTOM CONFIRMED
Fix validation (same fix.diff as DF-5347)
BUSDMA-PHANTOM: RESULT=FAILED (no callback fires; map dequeued+reservation released) BUSDMA-PHANTOM: cleanup done (guest stable)
DF-5348 VERDICT β REPRODUCED (phantom deferred load + callback on an unloaded map)
Root cause
sys/sys/bus_dma.h:313βbus_dmamap_unloadexpands to_bus_dmamap_unloadonly.sys/platform/pc64/x86_64/busdma_machdep.c:1022-1031β_bus_dmamap_unloadfrees active bpages and nothing else: it never removes the map frombz->bounce_map_waitinglist(busdma_machdep.c:651, the EINPROGRESS queueing site) and never resetsmap->pagesneeded/map->pagesreservedor releases the reservation taken byreserve_bounce_pages(busdma_machdep.c:1263).- The ONLY dequeue path is
get_map_waiting()(busdma_machdep.c:1408), driven by any bounce-page release (free_bounce_page:1399 /return_bounce_pages:1306) β even long after the driver aborted. busdma_swi()(busdma_machdep.c:1445) then executes the deferredbus_dmamap_loadwith the map's stashedbuf/buflen/callback(queue-time values, busdma_machdep.c:647-649) and invokes the driver callback (busdma_machdep.c:804).
Reproduced
KLD harness (POC_TEST=2), clean-boot DragonFly 6.5-DEVELOPMENT guest:
mapY load -> 36 (EINPROGRESS; mapY queued, holds partial reservation of 1 bpage) bus_dmamap_unload(mapY) returned -- map is STILL on the waiting list bus_dmamap_unload(mapX) frees a bpage -> wakes the UNLOADED mapY ... *** DEFERRED CALLBACK FIRED FOR UNLOADED MAP *** buf=0xd0000002 nsegs=2 error=0 RESULT=PHANTOM CONFIRMED
The callback fired for a transaction the driver had already unloaded; the
deferred load also bound a bounce page to the stale buffer (datavaddr)
inside the map. With a driver that frees its buffer on abort (the normal
pattern) this becomes a write-after-free via the subsequent
bus_dmamap_sync(PREWRITE/POSTREAD) bounce copies
(busdma_machdep.c:1046, 1057).
Impact
Concrete (demonstrated): driver-visible phantom callback after unload β double completion of aborted transactions, stale-buffer DMA setup, and leaked bounce-page reservations shrinking the shared zone (zone-exhaustion DoS). Driver-dependent worst case: write-after-free in whichever driver uses the map, on any bounce-page release (attacker-groomable timing).
Fix validation
Same fix.diff as DF-5347 (unload dequeues + releases reservation; busdma_swi drops reset maps). Patched kernel #1 (Oct 3 06:29): mapY's unload dequeues it, the later page free wakes nothing:
RESULT=FAILED <- phantom callback did NOT fire (fix works) cleanup done
Guest stable. Baseline behavior GONE.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedfix.diff (DF-5347 pack) applied to guest /usr/src, kernel rebuilt. Baseline: phantom callback fired for unloaded map. Patched: 'RESULT=FAILED' -- no callback after unload, reservation released, cleanup completed, guest stable.
dmesg_fixrun.txt (DF-5347 pack): BUSDMA-PHANTOM lines on patched kernel show RESULT=FAILED + cleanup done
Confirmed kernel references
Detail
Exploit chain
bouncing tag -> load returns EINPROGRESS (map queued holding partial reservation) -> driver aborts: bus_dmamap_unload (no-op) + buffer freed/reused -> any later bounce-page release in the zone -> get_map_waiting completes reservation + dequeues the unloaded map -> busdma_swi runs deferred load on stale buf -> driver callback fires for dead transaction / bounce copies touch the freed buffer
Evidence (decisive lines)
run.log.baseline: 'bus_dmamap_unload(mapY) returned -- map is STILL on the waiting list' then '*** DEFERRED CALLBACK FIRED FOR UNLOADED MAP ***' and 'RESULT=PHANTOM CONFIRMED'; patched kernel (dmesg in DF-5347/dmesg_fixrun.txt): 'RESULT=FAILED' + 'cleanup done'
PoC changes
authored fresh (no seed); POC_TEST=2 variant of the same KLD harness as DF-5347
Verified recommended fix
_bus_dmamap_unload: if pagesneeded||pagesreserved, STAILQ_REMOVE the map from the zone waiting list and call return_bounce_pages; busdma_swi: skip maps whose counters were reset (see fix.diff)
Verdict
bus_dmamap_unload() is a silent no-op for a map queued at EINPROGRESS: _bus_dmamap_unload only frees active bounce pages and never dequeues the map from bounce_map_waitinglist nor resets pagesneeded/pagesreserved nor releases its partial bounce-page reservation. Reproduced on a clean-boot DragonFly 6.5 guest: after unloading the queued map, releasing any bounce page in the zone completed its reservation and busdma_swi executed a full deferred bus_dmamap_load on the UNLOADED map, invoking its callback with stale buf/buflen state ('DEFERRED CALLBACK FIRED FOR UNLOADED MAP'). For drivers that free/reuse their buffer after aborting (the normal pattern) this is a write-after-free via the subsequent sync bounce copies; the stuck reservations also drain the shared bounce zone (DoS). impact recorded as dos (the driver-independent harm); the driver-dependent write-after-free ceiling is documented in VERDICT.md.
No comments yet.