{
  "finding_id": "DF-5370",
  "status": "reproduced",
  "reproduced": true,
  "impact": "dos",
  "confidence": "certain",
  "verdict": "bus_dmamap_destroy() in sys/platform/vkernel64/platform/busdma_machdep.c:383-392 frees a bus_dmamap that is still linked on bounce_zone->bounce_map_waitinglist after an EINPROGRESS deferred load (queued at :587-589); bus_dmamap_unload is equally a no-op for a queued map (macro calls only _bus_dmamap_unload:891-900, which walks the empty bpages list - the DF-5348 twin). Proven in a live VKERNEL64 vkernel on the guest with a compiled-in test driver: destroy of the queued map returned 0 (freed while queued), an immediate kmalloc(72) spray reclaimed the exact chunk, and the next bounce-page release (free_bounce_page:1156 -> get_map_waiting:1184 -> reserve_bounce_pages:1071) followed the PLANTED map->dmat pointer (attacker-chosen address, spinlock+counter writes through it) and wrote map->pagesreserved 0->1 into the freed chunk. The freed maps ghost reservation also permanently wedged the zone: a victim maps load returns EINPROGRESS forever while a bounce page sits on the free list (free_bpages==0) - recorded impact dos is the strongest fully-demonstrated end state; the underlying primitive is a vkernel-kernel write-after-free with controlled pointer dereference, the vkernel twin of pc64 DF-5347 (High, arbitrary ring-0 call). On the vkernel the deferred chain is cut off by the #ifdef notyet add_map_callback panic stub (pass-1 DF-1044, not re-reported here) unless the attacker keeps the reservation partial, as the PoC deliberately does to stay crash-free. Guest-of-guest reachability requires a bus_dma-using driver inside the vkernel (none in stock VKERNEL64; the file is compiled into every vkernel), hence Medium rather than the pc64 twins High.",
  "exploit_chain": "lowaddr-constrained bouncing tag (private zone, 3 pages) -> mapA load consumes 1 page -> mapB WAITOK load of a 7-page kernel buffer under zone pressure -> reserve_bounce_pages partial (2/7) -> map queued, EINPROGRESS -> driver error cleanup bus_dmamap_destroy(mapB) returns 0: kfree while linked (map_count--, chunk in 128B M_DEVBUF class) -> kmalloc(72) spray reclaims the chunk and plants pagesneeded=0x10000 / pagesreserved=0 / dmat=planted fake tag (bounce_zone -> real zone) -> bus_dmamap_unload(mapA) frees its bounce page -> free_bounce_page -> get_map_waiting pops the freed chunk -> reserve_bounce_pages dereferences the planted dmat (BZ_LOCK on attacker-chosen address, bz->free_bpages/reserved_bpages writes) and writes pagesreserved 0->1 into freed memory -> reservation stays partial so get_map_waiting returns NULL, avoiding the notyet add_map_callback panic -> zone permanently wedged (ghost reservation), all later loads on the zone EINPROGRESS forever. With a satisfiable planted pagesneeded the chain instead continues to dequeue + add_map_callback, which on this vkernel is panic(uncoded) (DF-1044); with the callback machinery implemented (as on pc64) this same chain yields the arbitrary ring-0 call of DF-5347.",
  "evidence": "run.log (pristine-source baseline): load B ret=36 (EINPROGRESS - mapB queued...), mirror check mapB pagesneeded=7 pagesreserved=2 ... OK, bus_dmamap_destroy(queued mapB) = 0 (FREED WHILE STILL QUEUED - UAF armed), reclaim: freed mapB=0x800e966068 fake=0x800e966068 SAME CHUNK - planted ... dmat=0x1120e20 (fully controlled), after wake: freed chunk pagesneeded=65536 pagesreserved=1, VERDICT PROVEN - kernel wrote pagesreserved (0->1) into the reclaimed chunk after following its PLANTED dmat pointer, victim mapC load err=36 (EINPROGRESS forever...), VERDICT wedge PROVEN. run.fixed.log (fix.diff): bus_dmamap_destroy(queued mapB) = 16 (EBUSY - fix active, UAF not armed) + fixed path: mapC load err=0 nsegs=1 - zone healthy. An earlier baseline also captured the no-reclaim variant: after wake: freed chunk pagesneeded=7 pagesreserved=3 (stale 2->3 write through the queued link).",
  "kernel_refs": [
    "sys/platform/vkernel64/platform/busdma_machdep.c:383",
    "sys/platform/vkernel64/platform/busdma_machdep.c:587",
    "sys/platform/vkernel64/platform/busdma_machdep.c:583",
    "sys/platform/vkernel64/platform/busdma_machdep.c:891",
    "sys/platform/vkernel64/platform/busdma_machdep.c:1156",
    "sys/platform/vkernel64/platform/busdma_machdep.c:1184",
    "sys/platform/vkernel64/platform/busdma_machdep.c:1071",
    "sys/sys/bus_dma.h:313"
  ],
  "poc_changes": "no seed PoC existed; driver authored fresh. Fixes vs first draft: (1) EINPROGRESS is 36 on DragonFly, and bus_dmamap_load returns 0 on non-EINPROGRESS errors (error only reaches the callback) - had to read cb_err; (2) large kmallocs are page-quantized so a +2048 interior offset forces uniform non-zero pmap_kextract offsets; (3) BOUNCE_ALIGN tags only bounce the first (partial) page of a linear buffer - the count loop samples pages at aligned starts - so the demo uses a lowaddr=16MB BOUNCE_LOWADDR tag where every heap page bounces; (4) DFly STAILQ_HEAD carries stqh_first AND stqh_last: sizeof(bus_dmamap)=72 and mirror offsets are pagesneeded=16/pagesreserved=20/dmat=24 - the 72-byte chunk lives in the 128-byte class, so the spray must kmalloc(72), not 64 (64-byte spray never reclaims); (5) vkernel needs /var/vkernel to exist and the mountroot prompt answered (ufs:vkd0) for a clean boot.",
  "attempts": 9,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64 (host guest; vkernel = nativekernel VKERNEL64, baseline kernel.debug MD5 22158900b2d66a0e08e02fb4257b4374)",
  "runtime_sec": 9600,
  "guest_dirty": 0,
  "build_cmd": "sh /root/build_kernel.sh   # installs dfbusdma.c, appends files/config entries, make -j6 nativekernel KERNCONF=VKERNEL64",
  "run_cmd": "sh /root/run.sh /tmp/vkconsole.baseline.log   # boots ./kernel.debug -m 512m -r /vkroot.img -n 1; tests run at SI_SUB_CONFIGURE",
  "code_hash": "bb24de35ada35f7d7e31aec07c65bb49cd7971e1871f3001ce8b62079738fcd7",
  "notes": "Deterministic, crash-free proof (vkernel ALIVE after the corrupted state, so the wedge is observable on a live system). Guest /usr/src busdma_machdep.c restored to pristine and baseline rebuilt/re-verified (run.log = that final pristine-source run); guest reset with-src afterwards. DF-1044 (notyet panic stub + return_bounce_pages wrong-map arg) deliberately not re-reported; the PoC keeps the reservation partial to avoid the stub. pc64 fix for this family is bus_dma_tag locking + segments caching (absent here - separate finding DF-5371).",
  "recommended_fix": "bus_dmamap_destroy: also return EBUSY when map->pagesneeded || map->pagesreserved (pending deferred load); _bus_dmamap_unload: dequeue the map from the bounce-zone waiting list and return its reservation; see fix.diff (validated: EBUSY + zone healthy)",
  "fix_status": "fixed",
  "fix_baseline_reproduced": true,
  "fix_patched_reproduced": false,
  "fix_verdict": "fix.diff (destroy EBUSY guard + unload dequeue/return-reservation) applied to guest /usr/src, VKERNEL64 rebuilt. Baseline: destroy=0, planted-dmat write-after-free (pagesreserved 0->1), zone wedged (mapC EINPROGRESS). Patched: bus_dmamap_destroy(queued mapB)=16 (EBUSY), unload(mapA)+unload(mapB) drain the waiting list, mapC load err=0 nsegs=1 zone healthy; no UAF, no wedge, vkernel ALIVE.",
  "fix_evidence": "run.fixed.log: DF5370: bus_dmamap_destroy(queued mapB) = 16 (EBUSY - fix active, UAF not armed) and DF5370: fixed path: mapC load err=0 nsegs=1 - zone healthy; fix-build.log full rebuild log; fix.diff is git-apply-able against the pristine sys/ tree (hunk offsets verified)"
}