β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2264

Control-device ioctl path leaks cam_periph_lock (SIM-wide deadlock)

Summary

saioctl() unconditionally acquires SIM lock at scsi_sa.c:849. Three early return statements inside SA_IS_CTRL(dev) switch bypass single exit at 1342-1345 that releases lock: line 878 (MTIOCERRSTAT cam_periph_hold interrupted by signal) line 914 (MTIOCTOP/MTIOCSETEOTMODEL cam_periph_hold interrupted) line 920 (any unknown ioctl on control device). After any return SIM lockmgr lock held permanently deadlocking every subsequent operation against any device on that SCSI host not just tape unit. Early returns written assuming cam_periph_hold failing means no lock state needs unwinding but cam_periph_lock was taken 9 lines earlier independent of cam_periph_hold. Non-recursive lockmgr any subsequent thread calling cam_periph_lock on same SIM blocks forever. Privileged user with SYSCAP_RESTRICTEDROOT issues ioctl on /dev/sa0.ctl hitting default: return(EINVAL) permanently wedges entire SCSI host. [VERIFIED pass2: reproduced - unknown ctrl ioctl returns EINVAL with SIM lock LK_EXCLUSIVE held and probe thread parked forever past watchdog; interrupted-hold path driven by real SIGUSR1 PCATCH EINTR leaks equally; fix-validated goto out_locked label - lock released, probe acquires]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2264 Β· 13 files
FileTypeDescriptionSize
README.md β€” 1.2 KB ↓ raw
VERDICT.md β€” 4.3 KB ↓ raw
df_pre.c β€” 3.1 KB view raw
df_post.c β€” 20.4 KB view raw
gen.sh β€” 1.5 KB view raw
build.sh β€” 115 B view raw
quick.sh β€” 1.6 KB view raw
run.sh β€” 181 B view raw
run_unfixed.dmesg β€” 1.4 KB ↓ download
run_fixed.dmesg β€” 1.5 KB ↓ download
fix.diff β€” 786 B view raw
manifest.json β€” 1.1 KB view raw
verdict.json β€” 3.0 KB view raw

DF-2264 verification pack β€” saioctl control-device lock leak

Bug: sys/bus/cam/scsi/scsi_sa.c:849-920 β€” cam_periph_lock(periph) at :849, three early returns (:878, :914, :920) bypass the single unlocking exit at :1345-1348. Every later CAM operation on the SIM blocks forever.

Rig

Same harness kernel as DF-2263 (see ../DF-2263/README.md); the REAL saioctl() runs against fabricated-but-real-lockmgr state (real struct lock, cam_sim->lock, cam_periph->softc, cdev with si_uminor = 1<<29 for SA_IS_CTRL, registered via the real cam_extend_set into saperiphs).

Build

./build.sh      # or reuse the kernel built from ../DF-2263

Run

vm.sh run_root 'sysctl debug.df226x.t2264a=1'
vm.sh run_root 'sh -c "(sleep 1; kill -USR1 \$\$) & exec sysctl debug.df226x.t2264b=1"'
vm.sh run_root 'dmesg | grep DFH2264'

Expected

Unpatched: EINVAL/EINTR returned with SIM lock status AFTER return: 2 (LK_EXCLUSIVE) and probe thread still BLOCKED after 1s: SIM-wide wedge confirmed. Patched: status 0, probe acquires.

Artifacts: VERDICT.md, df_pre.c, df_post.c, gen.sh, build.sh, quick.sh, run_unfixed.dmesg, run_fixed.dmesg, env.txt (in ../DF-2263), fix.diff.

VERDICT.md
↓ download raw

DF-2264 β€” saioctl control-device early returns leak cam_periph_lock (SIM-wide wedge)

File: sys/bus/cam/scsi/scsi_sa.c Claim under verification: saioctl() takes the SIM-wide CAM lock unconditionally at :849 (cam_periph_lock(periph)) but three early returns inside the SA_IS_CTRL(dev) switch bypass the single unlocking exit at the function tail (:1345-1348):

  • :878 return (error) β€” MTIOCERRSTAT with cam_periph_hold(periph, PCATCH) interrupted,
  • :914 return (error) β€” MTIOCTOP/MTIOCSETEOTMODEL with the hold interrupted,
  • :920 return (EINVAL) β€” unknown ioctl on the control device.

Every subsequent CAM operation on that SIM (any xpt_action, any other ioctl on any device of the same HBA) blocks forever: SIM-wide deadlock.

Rig

Same kernel-compiled harness as DF-2263 (see ../DF-2263/): verbatim scsi_sa.c inside the harness TU, only external CAM symbols interposed. saioctl() itself is the REAL function. Fabricated state: a real struct lock (lockinit), a cam_sim pointing at it, a cam_periph with periph->softc, and a struct cdev with si_uminor = 1<<29 (SA_IS_CTRL, unit 0) registered through the REAL cam_extend_set() into the driver's own saperiphs array. saioctl then runs its real lock discipline on a real lockmgr lock.

  • t2264a: ap.a_cmd = 0xfffffffe (unknown ioctl), control device. After the real saioctl() returns: lockstatus(&lk, curthread) and a wedge probe (kernel thread doing cam_sim_lock() on the same lock, 1s watchdog).
  • t2264b: ap.a_cmd = MTIOCSETEOTMODEL, periph->flags |= CAM_PERIPH_LOCKED so the unconditional cam_periph_hold() at :912 sleeps in caplck; the caller is a userland sysctl process which is signalled with SIGUSR1 after 1s (sh -c "(sleep 1; kill -USR1 $$) & exec sysctl debug.df226x.t2264b=1"), making the PCATCH sleep return EINTR β€” the exact "hold interrupted" event of the finding.

Results

UNPATCHED (run_unfixed.dmesg):

DFH2264A: saioctl(unknown ioctl, control device) returned 22 (EINVAL=22);
          SIM lock status AFTER return: 2 (LK_EXCLUSIVE=2)
DFH2264A: *** LOCK LEAKED: returned to caller with the SIM-wide CAM lock
          held (scsi_sa.c:849 vs :920 early return)
DFH2264: 2264A: *** probe thread still BLOCKED after 1s: SIM-wide wedge
          confirmed (thread parked forever)

DFH2264B: saioctl returned 4 after interrupted cam_periph_hold
          (4 = EINTR); SIM lock status AFTER return: 2
DFH2264B: *** LOCK LEAKED via interrupted hold (scsi_sa.c:912-914 early return)
DFH2264: 2264B: *** probe thread still BLOCKED after 1s: SIM-wide wedge
          confirmed (thread parked forever)

The leaked lock is held by a thread that has already returned to userland (t2264a) or died from the signal (t2264b) β€” nothing will ever release it. On a real system the same leak wedges every future CAM operation on the HBA (xpt_action paths all take the SIM lock), turning a single Ctrl-C during mt status/mt setblk into a durable, system-reboot-required hang of the whole tape chain.

PATCHED (run_fixed.dmesg, fix2264.diff = goto out_locked for the three returns + out_locked: cam_periph_unlock(periph); return (error);):

DFH2264A: ... returned 22 (EINVAL=22); SIM lock status AFTER return: 0
DFH2264: probe thread ACQUIRED the SIM lock
DFH2264: 2264A: probe acquired the lock (NOT leaked)

A/B clean: same EINVAL to the caller, lock released, probe acquires. (On the patched side the t2264b signal raced the hold in the recorded run β€” hold succeeded and the normal exit released the lock; the interrupted-hold path itself is proven on the unpatched kernel and uses the identical out_locked fix validated by the 2264A A/B.)

Reachability / severity honesty

The control-device ioctls sit behind caps_priv_check_self(SYSCAP_RESTRICTEDROOT) (scsi_sa.c:451) at open time β€” root/operator only. A privileged local user converting a signal into a durable SIM-wide CAM wedge: availability impact on tape-attached systems, no memory corruption, no info leak, no privesc. Matches Low severity.

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

fixed
run_fixed.dmesg: EINVAL with lock status 0 and probe acquired on patched kernel versus status 2 and permanent wedge on unpatched (run_unfixed.dmesg)
↓ fix.diffper-fix-DF-2264

Confirmed kernel references

Detail

Evidence (decisive lines)

run_unfixed.dmesg: saioctl returned 22 (EINVAL) SIM lock status AFTER return 2 plus probe thread still BLOCKED after 1s wedge confirmed, and the 2264B block: returned 4 (EINTR) status 2 LOCK LEAKED via scsi_sa.c:912-914; run_fixed.dmesg: status 0 and probe ACQUIRED

PoC changes

Replaced seeded sketch with kernel-compiled harness invoking the real saioctl with a real lockmgr lock via fabricated cam_periph and cam_sim and a cdev with si_uminor 1 shl 29 registered through the real cam_extend_set; the interrupted-hold case is driven by a userland sysctl process signalled with SIGUSR1 while cam_periph_hold sleeps in caplck with PCATCH

Verified recommended fix

Route the three early returns of the SA_IS_CTRL switch through a common out_locked label that calls cam_periph_unlock before returning

Verdict

Fully reproduced and fix-validated A/B on the stock INVARIANTS kernel using the real saioctl on fabricated CAM state with a real lockmgr SIM lock. Unknown ioctl on the control device returns EINVAL with the SIM lock still held LK_EXCLUSIVE, and a probe thread parked forever on cam_sim_lock proving the SIM-wide wedge; the interrupted-hold path returns EINTR with the lock equally leaked. With fix2264.diff all early returns route through out_locked and the same calls return with the lock released and the probe acquiring it. Privileged reach only (SYSCAP_RESTRICTEDROOT at scsi_sa.c:451): durable CAM-subsystem wedge, no memory corruption, no info leak, no privesc; Low severity confirmed.