⬢ DragonFlyBSD Kernel Audit
DF-4060 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4060",
  "status": "reproduced",
  "reproduced": true,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "REPRODUCED via the shared production-handler KLD dispatch rig: production amr_std_poll_command (nm-resolved, prologue-verified) runs over a fabricated post-attach softc with real bus_dma and a production command cluster while the virtual card models the stall (accepts the command, never completes). After the ~1.03 s timeout: AMR_CMD_BUSY still set, amr_busycmd[slot]==ac, amr_busyslots==1 (amr.c:1356-1362 slot now permanently busy); the callers unconditional amr_releasecmd (amr_enquiry:1058-61, amr_flush:1097-1100, amr_support_ext_cdb:1140-43, amr_dump_blocks:2380-82) pushes the still-busy command onto the free list; 100 further production amr_done calls never drain busyslots (amr_rescan_drives amr.c:460-463 livelock precondition); the next amr_alloccmd returns the same command and production amr_start hits its guard: panic(\"amr: slot 2 busy?\") at amr_start+0x18b (amr.c:1742-43) -- deterministic guest death after any following driver command. Late-completion variant: after timeout+release, a stale completion drives production amr_done to freeslot + store status 0x77 (amr.c:1795-98) + run the completion callback ON a command that still sits on amr_freecmds (two owners; probe fired with ON FREE LIST=1) -- the findings wakeup/status stores into a live unrelated command for a recycled command. Fix (detach slot under amr_hw_lock on timeout, clear BUSY) validated on a rebuilt nativekernel: busyslots=0 after timeout, recycled amr_start returns 0 (no panic), stale completion tolerated as bad-slot, probe not fired, guest alive.",
  "exploit_chain": "controller stalls one polled command ~1 s -> amr_std_poll_command timeout leaves slot busy (busycmd/busyslots/BUSY) -> every caller amr_releasecmd()s the still-busy command onto the free list -> (a) next amr_alloccmd/amr_start: production panic amr: slot %d busy? (deterministic); (b) late completion completes the recycled command while free-listed / re-issued (status store + completion callback into a live unrelated command, racy double completion); (c) amr_rescan_drives (root ioctl path amr.c:896-897) spins forever on the poisoned counter. Root-only surface + card precondition; ceiling is guaranteed crash + racy UAF-flavored double completion, no unpriv->root.",
  "evidence": "VERDICT.md (full narrative); bootA_serial.log + panic_4060.txt ([amrrig] DF-4060 FINALE: production amr_start on the recycled command (slot 2 still busy) -> panic: amr: slot 2 busy? amr_start+0x18b); bootA tmo/late outputs in VERDICT.md + dmesg_patched.txt ([4060TMO] after timeout: BUSY_still_set=1 busycmd_holds_ac=1 busyslots=1, busyslots after 100x amr_done=1, recycled_same_cmd=1, [4060LATE] probe fired=1 ... completion status 0x77 stored ... ON FREE LIST at callback 1); fix_run.log (patched: BUSY_still_set=0 busyslots=0, start_err=0 no panic, late tolerated, uptime)",
  "kernel_refs": [
    "sys/dev/raid/amr/amr.c:1336",
    "sys/dev/raid/amr/amr.c:1353",
    "sys/dev/raid/amr/amr.c:1355",
    "sys/dev/raid/amr/amr.c:1356",
    "sys/dev/raid/amr/amr.c:1359",
    "sys/dev/raid/amr/amr.c:1361",
    "sys/dev/raid/amr/amr.c:1742",
    "sys/dev/raid/amr/amr.c:460",
    "sys/dev/raid/amr/amr.c:462",
    "sys/dev/raid/amr/amr.c:1058",
    "sys/dev/raid/amr/amr.c:1097",
    "sys/dev/raid/amr/amr.c:1795"
  ],
  "poc_changes": "No seed sources existed (pack was source-only hardware-gated). Shared rig with DF-4059 (identical dfamr_rig.c/dfamr_ctl.c); this finding exercises the tmo/late ioctls. The stall is modeled by the virtual card consuming the POST and never raising SINTR; the timeouts 1001 production amr_done calls poll production amr_std_get_work each iteration.",
  "attempts": 3,
  "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": 5400,
  "guest_dirty": 0,
  "build_cmd": "make (bsd.kmod.mk, KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC) && cc -O2 -Wall -o dfamr_ctl dfamr_ctl.c   [see build.sh]",
  "run_cmd": "./dfamr_ctl prime && ./dfamr_ctl offs && ./dfamr_ctl magic && ./dfamr_ctl tmo 0 && ./dfamr_ctl late && ./dfamr_ctl tmo 1   [see README.md]",
  "code_hash": "9caf52e072fed47846e29e607ef1c26ea16486b3ce515cc94d9af390c0107483",
  "notes": "The late-completion probe (ac_complete armed on the free-listed command, a stand-in for amr_completeio) was needed because production amr_complete -> amr_startio -> amr_bio_command -> amr_alloccmd immediately reallocates the very same command off the free-list head and its field-clear (amr.c:1895) wipes ac_status -- itself part of the double-lifecycle confusion. Sibling leak shapes exist in the quartz poll paths (amr_quartz_poll_command bus_dmamap_load failure amr.c:1421-25, adapter is busy return amr.c:1448-56 -- amr_start already marked the slot busy); same guard pattern applies but out of the filed scope. Livelock demo is bounded (100 amr_done calls show the counter cannot drain); running the real amr_rescan_drives loop would wedge the guest forever by design.",
  "recommended_fix": "On the amr_std_poll_command timeout path, detach the command from its slot under amr_hw_lock (if busycmd[slot] == ac: clear it and decrement amr_busyslots) and clear AMR_CMD_BUSY so late completions are tolerated as bad-slot events",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff (shared with DF-4059 pack; git-apply clean) applied to guest /usr/src, nativekernel rebuilt (RC=0) and installed; on the patched kernel the rigs tmo scenario WITH the former panic finale returns start_err=0 with no panic (slot detached: BUSY=0, busycmd cleared, busyslots=0; livelock precondition gone), the late completion is tolerated as bad slot 2 completed with the probe never firing, and the guest stays up",
  "fix_evidence": "fix.diff; fix_build.log (full nativekernel output, RC=0/INSTALL_RC=0); fix_run.log (baseline-vs-patched contrast); dmesg_patched.txt"
}