⬢ DragonFlyBSD Kernel Audit
DF-5377 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5377",
  "status": "reproduced",
  "reproduced": true,
  "impact": "dos",
  "confidence": "certain",
  "verdict": "REPRODUCED: permanent kernel resource leak -> system-wide raw-I/O wedge. targsendccb() maps daemon CTIO data via cam_periph_mapmem() (getpbuf_mem bounce pbuf recorded in descr->mapinfo, cam_periph.c:643-767), but the teardown paths that discard CCBs -- targdone()s not-enabled free path (scsi_target.c:828-832) and targdtor()s user_ccb_queue drain (:541-548) -- call targfreeccb() directly, dropping mapinfo->bp[0]; only targreturnccb() (:973-974) unmaps. Deterministic choreography (no race, no close, DF-1045 not involved): per cycle TARGIOCENABLE -> write() CTIO (CAM_DIR_IN, 512B) -> targsim holds it -> TARGIOCDISABLE (rig fails the XPT_ABORT with CAM_UA_ABORT so the CTIO survives; EN_LUN disable clears LUN_ENABLED) -> rig completes the held CTIO -> targdone sees not-enabled -> targfreeccb without unmap -> pbuf leaked. Guest proof: vm.pbuf_mem_count drops by exactly one per cycle (394 -> 386 after 8) and NEVER recovers; draining all 394 wedges every getpbuf_mem() consumer -- dd if=/dev/vbd0 sits unkillable in D0 on wchan wswbuf5 (vm/vm_pager.c:505-528), i.e. raw physio I/O system-wide is dead until reboot. Fix validated after two refuted intermediate designs (v1: cam_periph_unmapmem in targdone is illegal from the SWI -- copyout trap; v2: relpbuf in targdone trips lockmgr owner assert since the pbuf is BUF_LOCKed by the mapping thread): final fix routes not-enabled completions to user_ccb_queue and unmaps in targdtor; patched kernel keeps the pool at baseline 394 across the same cycles.",
  "exploit_chain": "privileged daemon loop (one fd): enable -> write CTIO w/ mapped 512B buffer -> disable (abort fails by rig, disable succeeds) -> late CTIO completion -> targdone not-enabled path -> targfreeccb drops pbuf -> repeat N times -> vm.pbuf_mem_count 394 -> 0 -> every raw physio I/O (dd/fdisk/label probes on any disk) blocks forever in getpbuf_mem (wswbuf5), unkillable D state; system raw-I/O DoS until reboot.",
  "evidence": "run.log (394->386 after exactly 8 cycles, no recovery); run.drain.log (pool to 0; dd unkillable D0 wswbuf5); run.patched.log (final-fix kernel: pool returns to 394, [+] FIXED); targsim.c (abort_ok=0 + hw.targsim.complete choreography knobs).",
  "kernel_refs": [
    "sys/bus/cam/scsi/scsi_target.c:828",
    "sys/bus/cam/scsi/scsi_target.c:541",
    "sys/bus/cam/scsi/scsi_target.c:973",
    "sys/bus/cam/scsi/scsi_target.c:788",
    "sys/bus/cam/cam_periph.c:643",
    "sys/vm/vm_pager.c:505"
  ],
  "poc_changes": "No seed existed. Rig adds: XPT_ABORT -> CAM_UA_ABORT policy (held CTIO survives TARGIOCDISABLE), sysctl hw.targsim.complete to xpt_done held CCBs late, hw.targsim.held_total so userland waits deterministically for targstart. readback mode added for fix validation (drain user_ccb_queue via read()).",
  "attempts": 4,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Sat Oct 3 17:18:54 UTC 2026 x86_64 (baseline) / #2,#3 (fix iterations)",
  "runtime_sec": 2400,
  "guest_dirty": 0,
  "build_cmd": "cd /root/targpwn && make KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC && cc -O0 -g -o poc5377 poc5377.c",
  "run_cmd": "cd /root/targpwn && kldload ./targsim.ko && sleep 2 && sysctl vm.pbuf_mem_count && ./poc5377 8 && ./poc5377 386 drain",
  "code_hash": "3ab75bdf049fb7d316054ab6f026d460162d8b67d0419e4c15dd810300ffdd8f",
  "notes": "The unpatched kernel never returns the pbuf (verified: count stays low across minutes). Fix engineering lesson recorded: releases of these pbufs must run in the daemon thread context (copyout + BUF_UNLOCK ownership constraints); v3 fix achieves that via user_ccb_queue delivery + targdtor unmap, matching the API contract in scsi_targetio.h.",
  "recommended_fix": "Route not-enabled targdone() completions to user_ccb_queue (instead of freeing in SWI context) and cam_periph_unmapmem() any still-mapped buffers in targdtor() before targfreeccb().",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Baseline: -1 pbuf per cycle permanently; full drain (394->0) leaves dd in unkillable D0/wswbuf5. Patched v3 kernel: after the identical 8-cycle choreography the pool returns to baseline 394 (at most one outstanding during the run), [+] FIXED: pool fully restored. v1/v2 intermediate fix designs were themselves refuted live (copyout trap in SWI; lockmgr owner assert in relpbuf from SWI) and are documented in run.patched.log.",
  "fix_evidence": "run.log + run.drain.log (baseline leak + wedge); run.patched.log (v3 kernel, pool restored).",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #3: Sat Oct 3 18:32:37 UTC 2026 x86_64"
}