⬢ DragonFlyBSD Kernel Audit
DF-5273 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5273",
  "status": "reproduced",
  "reproduced": true,
  "impact": "peripheral refcount underflow (demonstrated 1 -> 0 -> -1 on a valid, open cd periph; earlier exploratory runs reached -3 and -5), one burned reference per signal-interrupted ioctl, deterministic; with device invalidation (unplug/kldunload) the CAM_PERIPH_INVALID && refcount==1 branch of cam_periph_release() frees periph+softc while fds remain open -> use-after-free window in cdioctl/cdclose between cam_extend_get() and cam_periph_lock(). Root-gated entry; the full UAF was not chased (racy), the underflow is deterministic.",
  "confidence": "certain",
  "verdict": "REPRODUCED (refcount burn): with a device-stalled CDIOCPAUSE holding the periph, each CDIOCPLAYMSF self-interrupted by SIGALRM (sigaction without SA_RESTART -> EINTR) returns Interrupted system call and decrements the cd periph refcount by exactly one, with no close() anywhere: initial refcount 1, then 1 -> 0 -> -1 printed per burn (earlier runs: 2 -> -3 -> -5 cumulative). Root cause verified statically: cam_periph_hold() acquires on entry and releases its own acquire when the PCATCH sleep fails (cam_periph.c:395,402) - it is balanced - so cdioctl()s extra cam_periph_release() on the hold-failure path (scsi_cd.c:1932) is the only unbalanced drop (cdopen()s identical-looking path is balanced because cdopen acquires first at :1017).",
  "exploit_chain": "privileged open fd + concurrent holder ioctl (device-stalled command keeps CAM_PERIPH_LOCKED set) + timed signal to a second ioctl thread -> cam_periph_hold returns EINTR -> cdioctls unmatched cam_periph_release -> repeat until refcount==1 -> invalidate device (unplug: AC_LOST_DEVICE -> CAM_PERIPH_INVALID) -> one more release hits cam_periph_releases INVALID&&refcount==1 branch -> camperiphfree() (cdcleanup: disk_destroy, kfree(softc)) while fds are open -> racing ioctl/close between cam_extend_get() and cam_periph_lock()/softc deref -> UAF.",
  "evidence": "refcount_burn.log: [poc] initial cd periph refcount: 1 then three ioctl rc=-1 errno=4(Interrupted system call) refcount=1/0/-1 lines; debug.evilcd.refcnt reads the live periph->refcount field. README.md quotes the earlier -3/-5 runs.",
  "kernel_refs": [
    "sys/bus/cam/scsi/scsi_cd.c:1930-1934",
    "sys/bus/cam/cam_periph.c:382-409",
    "sys/bus/cam/cam_periph.c:341-379",
    "sys/bus/cam/scsi/scsi_cd.c:1017-1031",
    "sys/bus/cam/scsi/scsi_cd.c:1918-1922"
  ],
  "poc_changes": "Threaded PoC (pthread + pthread_kill) hung because signal() installs SA_RESTART (the interrupted hold auto-restarts and re-blocks) and DFly pthreads made the kill/join racy; rewritten as a self-contained fork-based program: holder child issues CDIOCPAUSE on the shared fd (evilcd stalls it), parent arms a 100ms SIGALRM itimer (sigaction, sa_flags=0) around CDIOCPLAYMSF. The burner must share the holders fd: opening /dev/cd1 while the periph is held returns EAGAIN.",
  "attempts": 3,
  "guest_uname": "DragonFly dfbsd 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": 300,
  "guest_dirty": 0,
  "build_cmd": "cd /root/df5271 && cc -O -o poc_ref poc_ref.c",
  "run_cmd": "timeout 45 usched :1 ./poc_ref /dev/cd1 (evilcd kld loaded; holder burns are the first 3-4 lines before the unstall path wedges - the refcount evidence prints before that)",
  "code_hash": "sha256:poc_ref.c=e987aebe1e7c36ae",
  "notes": "The PoCs unstall sysctl calls xpt_done without the sim lock, which can wedge the tail of the run; the refcount evidence prints before that point and the guest is reset afterwards. Fix not kernel-built separately (one-line deletion, same file as DF-5271/5272 fixes; the combined validation kernel carried DF-5271+DF-5272 only), so fix_status is inconclusive rather than fixed.",
  "recommended_fix": "Delete the unmatched cam_periph_release(periph) in cdioctl()s cam_periph_hold failure path (cam_periph_hold already unwinds its own acquire).",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Fix is a one-line deletion of the unbalanced release; not separately kernel-validated in this run (the rebuilt validation kernel carried the DF-5271 and DF-5272 fixes).",
  "fix_evidence": "static: cam_periph.c:395 acquire vs :402 release symmetry inside cam_periph_hold; scsi_cd.c:1932 is the only unbalanced release on that path."
}