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)
PoC verification
Evidence pack
findings/poc/DF-2264 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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 withcam_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 realsaioctl()returns:lockstatus(&lk, curthread)and a wedge probe (kernel thread doingcam_sim_lock()on the same lock, 1s watchdog). - t2264b:
ap.a_cmd = MTIOCSETEOTMODEL,periph->flags |= CAM_PERIPH_LOCKEDso the unconditionalcam_periph_hold()at :912 sleeps incaplck; the caller is a userlandsysctlprocess 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
fixedrun_fixed.dmesg: EINVAL with lock status 0 and probe acquired on patched kernel versus status 2 and permanent wedge on unpatched (run_unfixed.dmesg)
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.
No comments yet.