{
  "finding_id": "DF-5083",
  "status": "reproduced",
  "reproduced": true,
  "impact": "dos",
  "confidence": "certain",
  "verdict": "ata_dmaload() (ata-dma.c:251-254) treats bus_dmamap_load()==EINPROGRESS as terminal while DragonFly busdma keeps the map queued with the drivers stack callback arg, the setprd callback and the request buffer armed; busdma_swi() later re-runs the load and fires ata_dmasetprd into the dead stack frame (write target = stale &dmatab word) and over the completed requests freed buffer. Proven on the guest with natas exact tag geometry: deferred PRD write through the stale word reproduced 3/3 (marker writes and, when the stale word held garbage, Fatal trap 12 supervisor-write at the ata-dma.c:210 PRD instruction in SWI context), freed-buffer reload reproduced, and the unsatisfiable-queue variant wedges the channel permanently (every later DMA load returns EINPROGRESS) \u2014 the on-path DoS. Full uid=0 chain not developed: needs control of the dead frames contents at fire time; recorded as primitive-level escalation potential.",
  "exploit_chain": "(1) >4GB-RAM nata/IDE box, request pages above the 32-bit bounce threshold (steady state under memory pressure); (2) a >16-bounce-page transfer (17-page-straddling 64KB IO on classic IDE, trivially any large IO on nata-ahci/marvell/promise/siil whose max_iosize overrides outgrow the 16-page pool) gets EINPROGRESS and is queued forever -> permanent channel wedge (demonstrated, DEMO C); (3) a satisfiable queue (any concurrent reservation on the shared bounce zone) fires busdma_swi after the request completed: setprd writes 8*nsegs PRD bytes through the dead frames dmatab word plus two 4-byte writes into the live threads stack (demonstrated, DEMOs A/B incl. NULL-target panic) \u2014 kernel memory corruption whose destination is governed by stale stack contents; (4) [not developed] groom the dead frame (ata request-service thread stack) to aim the PRD write / weaponize the stale-buffer reload into DMA-against-reused-pages for uid=0.",
  "evidence": "run.log (=run1.out): full clean run \u2014 DEFERRED busdma_swi CALLBACK FIRED into DEAD FRAME, marker[] filled with real PRD entries (00129000:00001000...), CHANNEL WEDGED FOREVER. run.2.log: 3/3 stability. panic-run-B.txt + run.3.log: Fatal trap 12 supervisor write at po_setprd+0x48 (the ata-dma.c:210 write), current process = Idle (SWI fired after frame death), and DEMO F fix-geometry 0/16 deferred. VERDICT.md: full narrative incl. on-path limitation.",
  "kernel_refs": [
    "sys/dev/disk/nata/ata-dma.c:223",
    "sys/dev/disk/nata/ata-dma.c:251-254",
    "sys/dev/disk/nata/ata-dma.c:199-216",
    "sys/dev/disk/nata/ata-lowlevel.c:126-134",
    "sys/platform/pc64/x86_64/busdma_machdep.c:776-787",
    "sys/platform/pc64/x86_64/busdma_machdep.c:645-656",
    "sys/platform/pc64/x86_64/busdma_machdep.c:1437-1450",
    "sys/platform/pc64/x86_64/busdma_machdep.c:1086-1095"
  ],
  "poc_changes": "Seed concept only; module written from scratch: nata data_tag geometry (alignment 2/boundary 64KB/maxsize 64KB/nseg 256) with BUS_DMA_PRIVBZONE + 256MB lowaddr for a deterministic trigger (real nata: shared zone, 32-bit lowaddr, needs >4GB-resident buffers this guests bottom-up allocator wont give the kernel heap); holder-map pool pressure via BUS_DMA_ONEBPAGE map create; per-demo tags; DEMO F validates the fix construction. straddle.c (+errno.h fix) is the on-path attempt.",
  "attempts": 8,
  "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",
  "runtime_sec": 3900,
  "guest_dirty": 0,
  "build_cmd": "cd /root/df5083 && sh build.sh  (make -DNO_WERROR KLD df5083.ko; cc -O -o straddle straddle.c)",
  "run_cmd": "kldload /root/df5083/df5083.ko; sleep 8; dmesg | grep DF5083",
  "code_hash": "bfa0ac3fcfeca1dc1de8dd7467973e5adab7c6a01478cf94c3cd8ee938259912",
  "notes": "Impact recorded as dos (the certain on-path outcome) with the memory-corruption primitive demonstrated (deferred write through attacker-governed stale stack word + reload over freed KVA); uid0 chain not completed. On-path trigger needs DMAd buffers above 4GB: not arrangeable on this guest for natas own tag (kernel heap allocates bottom-up; 256MB of clean CD reads stayed low), so the mechanism was proven at the exact nata geometry via the module. Sibling: DF-4191 (sdhci, same class, already filed). Core-level alternative fix worth considering: busdma honoring BUS_DMA_NOWAIT for real maps would close this class for every driver.",
  "recommended_fix": "Private bounce zone (BUS_DMA_PRIVBZONE) + one extra page of data_tag maxsize so the pool always covers a worst-case straddling max_iosize transfer, making bus_dmamap_load deferral impossible by construction; fail-stop panic if EINPROGRESS ever occurs.",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff construction validated at mechanism level in-kernel (module DEMO F: 16/16 worst-case 17-page-straddle loads on the fixed geometry completed synchronously, EINPROGRESS 0/16, deferred callback cannot arm \u2014 run.3.log). A patched-kernel build was not exercised because the on-path trigger does not manifest on this guest (kernel-heap disk buffers stay below 4GB), so a rebuilt kernel would show nothing the geometry demo does not.",
  "fix_evidence": "run.3.log: DF5083: F: 16x worst-case straddle loads on FIXED geometry: EINPROGRESS=0/16; fix.diff applies to ata_dmaalloc() data_tag creation + defensive EINPROGRESS panic in ata_dmaload()."
}