⬢ DragonFlyBSD Kernel Audit
DF-5240 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5240",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "REPRODUCED with deterministic kernel panic and DDB backtrace. xpt_scan_bus()s xpt_create_path() failure branch (sys/bus/cam/cam_xpt.c:5387-5398) frees the most recently issued XPT_SCAN_LUN CCB (work_ccb was reassigned at :5400; it is NOT the PATH_INQ CCB) and scan_info while target 0s SCAN_LUN CCB is still owned by its probe peripheral and still in flight. When the probe later completes that CCB, probedone() xpt_done()s it and the SWI invokes xpt_scan_bus() case XPT_SCAN_LUN on the freed CCB: xpt_setup_ccb() runs callout_init(ccb_h->timeout_ch) where timeout_ch was NULLed by xpt_free_ccb (:4941-4947) -> supervisor write to 0x10 -> Fatal trap 12. Backtrace: _callout_init <- xpt_setup_ccb <- xpt_scan_bus <- camisr_runqueue <- swi_cambio. The freed 24-byte scan_info is additionally dereferenced (scan_info->cpi->max_lun :5483) and written (scan_info->counter-- :5499) in the same completion path (demonstrated by 0x41 spray); with heap grooming this is a corruption primitive beyond the panic. Trigger is a bus scan racing SIM deregistration (device unplug / detach during scan), simulated exactly by the evilsim kld. Exploitation to uid=0 was not pursued: the primitive is a race-gated UAF whose deterministic outcome is a kernel panic; primary deliverable achieved (primitive proven, root-cause line-confirmed, fix validated).",
  "exploit_chain": "evil SIM kld registers parallel-scan bus (max_target=2) -> boot of XPT_SCAN_BUS -> target 0 SCAN_LUN issued; its probes first INQUIRY stalls in sim_action which sets CAM_SIM_DEREGISTERED -> scan loops xpt_create_path(target 1) fails (xpt_alloc_device NULL at cam_xpt.c:5083) -> failure branch kfrees scan_info and xpt_free_ccb()s the IN-FLIGHT SCAN_LUN CCB (timeout_ch=NULL) -> module sprays freed 24B scan_info with 0x41 -> completes stalled INQUIRY -> probe runs to completion -> probedone pops request_ccbs and xpt_done()s the freed CCB -> camisr -> xpt_scan_bus case XPT_SCAN_LUN -> xpt_setup_ccb on freed CCB -> callout_init(NULL) -> write fault 0x10 -> panic (3/3 runs; DDB trace captured on run 3).",
  "evidence": "panic.txt (Fatal trap 12, fault va 0x10, supervisor WRITE, Stopped at _callout_init + DDB trace showing xpt_scan_bus<-xpt_setup_ccb<-_callout_init via camisr_runqueue/swi_cambio); run.log (same panic from the plain vm.sh guest, all [EVILSIM] markers incl. sprayed 128 x 24-byte chunks with 0x41); evilsim.c (choreography source); build.log (clean kld build); fix.diff + VERDICT.md fix-validation section (patched kernel: PoC completes without trap).",
  "kernel_refs": [
    "sys/bus/cam/cam_xpt.c:5387",
    "sys/bus/cam/cam_xpt.c:5390",
    "sys/bus/cam/cam_xpt.c:5394",
    "sys/bus/cam/cam_xpt.c:5396",
    "sys/bus/cam/cam_xpt.c:5400",
    "sys/bus/cam/cam_xpt.c:5405",
    "sys/bus/cam/cam_xpt.c:5483",
    "sys/bus/cam/cam_xpt.c:5499",
    "sys/bus/cam/cam_xpt.c:5083",
    "sys/bus/cam/cam_xpt.c:4941",
    "sys/bus/cam/cam_xpt.c:3906"
  ],
  "poc_changes": "Seed sketch (none existed for this pass-2 finding): wrote evilsim.c kld from scratch. Fixed during bring-up: (1) kld needs opt_cam.h in SRCS; (2) initial run panicked in xpt_done (path->periph NULL) because the scan request path was created with periph=NULL -- real callers (xptioctl CAMIOCOMMAND :1062, xptconfigfunc :7073) use xpt_periph; module now does the same; (3) use cam_sim_lock()/cam_sim_unlock() instead of CAM_SIM_LOCK macro on a bare lock; (4) immediate CCBs (XPT_PATH_INQ, XPT_GET_TRAN_SETTINGS) must NOT be xpt_done()d -- only XPT_FC_QUEUED funcs.",
  "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": 2100,
  "guest_dirty": 0,
  "build_cmd": "cd /root/df5240 && make   (kld build; see build.log)",
  "run_cmd": "cd /root/df5240 && kldload ./evilsim.ko   (as root; kernel panics within seconds)",
  "code_hash": "eaebcf5e2d3a92f3bd96f79f63b0a0e68ef87623a2d7e7e692e43ecfdbe52409",
  "notes": "Deterministic 3/3 panic. Impact recorded as panic because the deterministic outcome on this guest is a kernel page fault; the underlying primitive is UAF of a live union ccb plus UAF-write of scan_info->counter, so the realistic worst case is kernel memory corruption. Trigger precondition is a hotplug race (device unplug/detach during scan) - not reachable by an unprivileged local user without device removal, hence not classified privesc. fix.diff validated by in-guest kernel rebuild (see fix_verdict).",
  "recommended_fix": "In xpt_scan_bus()s create-path failure branch, only free scan_info/the PATH_INQ CCB when no SCAN_LUN CCB has been issued yet (i == 0); otherwise subtract the never-issued count from scan_info->counter, NULL scan_info->request_ccb, and let the last completing CCB run the normal done cleanup (which must skip re-completing a NULL request_ccb).",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "FIXED. Baseline (stock kernel #0): PoC panics 3/3 (Fatal trap 12, supervisor write to 0x10 in _callout_init via xpt_setup_ccb<-xpt_scan_bus; DDB trace in panic.txt). Patched: git apply fix.diff in /usr/src, make -j6 nativekernel KERNCONF=X86_64_GENERIC (rc=0, 35761 lines, 0 errors under -Werror; /root/kbuild5240.log), make installkernel, reboot into kernel #1 (built 15:00:59), re-ran the byte-identical PoC: all choreography markers present (stall, DEREGISTERED, create_path failure 0x34, 128x0x41 spray, INQUIRY completion) and [EVILSIM] survived 10s: NO PANIC - guest stayed up, uptime healthy. The previously-observed bad behavior is gone.",
  "fix_evidence": "run.patched.log (full serial console of the patched run: markers at lines 49-54 + [EVILSIM] survived 10s: NO PANIC (UAF did not trigger) at line 60, no Fatal trap anywhere); run.log/panic.txt (baseline panic); fix.diff."
}