DF-5273 / verdict.json
{ "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." } |