sgread: tsleep on local stack address &hstat has no wakeup source; read() blocks indefinitely (local DoS)
Summary
In sgread() when user-supplied pack_id does not match any queued sg_rdwr the code drops periph lock and calls tsleep(&hstat PCATCH sgnull 0). hstat is uninitialized stack local. The wakeup address &hstat is unique to this stack frame; sgdone() only ever calls wakeup(rdwr) or wakeup(&done_ccb->ccb_h.cbfcnp) - never wakeup(&hstat). Therefore this tsleep can only return via a signal (PCATCH) i.e. read() blocks indefinitely until process is signaled. hstat uninitialized at tsleep time makes channel identifier accidental. Self-DoS / process-table pressure vector. [VERIFIED pass2: reproduced - unmatched pack_id read parks in wchan sgnull indefinitely with no wakeup source in tree; SIGTERM aggravation moves parked reader to R4 spin and livelocks guest within 2 min; bounded-sleep fix-validated]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2283 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| sg2283.c | β | 3.4 KB | view raw | |
| run.sh | β | 130 B | view raw | |
| run.log | β | 1.9 KB | view raw | |
| env.txt | β | 397 B | view raw | |
| fix.diff | β | 832 B | view raw | |
| verdict.json | β | 3.0 KB | view raw | |
| README.md | β | 2.0 KB | β raw |
DF-2283 β sgread() tsleep(&hstat) has no wakeup source; read() blocks indefinitely
What (sys/bus/cam/scsi/scsi_sg.c:763-806)
u_short hstat (:763) is an uninitialized stack local; when the
user-supplied pack_id matches nothing on softc->rdwr_done, sgread drops
the periph lock and does tsleep(&hstat, PCATCH, "sgnull", 0) (:790) β
timo 0, and the ONLY wakeups in the driver are sgdone's wakeup(rdwr)
(:363) / wakeup(&ccb_h.cbfcnp) (:346,:367) and sgstart's
wakeup(&periph->ccb_list) (:329). Nothing ever calls wakeup(&hstat)
(rg over sys/: zero hits) β the channel is a stack address unique to the
sleeping invocation.
Reproduce (guest, root β /dev/sg0 is 0600 root:operator and sgopen
gates on SYSCAP_RESTRICTEDROOT at :385)
cc -O -Wall -o sg2283 sg2283.c
./sg2283 block # control read matches pack_id -> returns at once
# test read(pack_id=4242) parks in "sgnull" forever
ps -o pid,stat,wchan -p <pid> # S4/I4 sgnull
Result (run.log)
- CONTROL read(pack_id=2002) returned immediately, result=0
- TEST read(pack_id=4242): parked in wchan
sgnullfor the whole observation window (23 s; timo=0) β indefinite block proven - BONUS aggravation (documented in run.log): delivering SIGTERM moved the reader to a kernel-bound R4 spin and the guest subsequently livelocked (sshd dead, serial silent, qemu alive) β the signal-vs-sgnull interaction turns the indefinite sleep into a potential whole-system livelock on this kernel.
Impact
Self-DoS of the reading process (and, per the aggravation note, a possible system livelock when the sleeper is signaled). RESTRICTEDROOT / operator-group gated on typical installs; on this guest /dev/sg0 is 0600 root-only, so the unprivileged actor is excluded (same gate family as DF-2281/2282).
Fix
fix.diff bounds the sleep with sc->sg_timeout and returns ENOENT on
timeout (a timeout proves no wakeup source exists for this request).
Validated on the fix kernel: see fix_validation in VERDICT.md.
Fix verification
fixedConfirmed kernel references
Detail
Evidence (decisive lines)
run.log: control read result=0 vs test read parked in wchan sgnull (S4 then I4 at +3s/+23s); run.log section run2: after SIGTERM the reader shows R4 (running, kernel priority) and the guest subsequently became unresponsive; env.txt: /dev/sg0 crw------- root operator
PoC changes
v1 blocked the whole ssh session because the forked child inherited stdout; v2 parks the reader under nohup and observes it with external ps probes, keeping one fd open for the whole session to avoid interleaving with the DF-2282 close-underflow. The signal experiment was added after the first guest wedge.
Verified recommended fix
Bound the &hstat sleep with sc->sg_timeout and return ENOENT on timeout (fix.diff); validated on the fix kernel: the same read returns errno=2 after the 60 s command timeout instead of parking forever (fix_validation: fixed).
Verdict
REPRODUCED on the stock INVARIANTS kernel: sgread() parks read(2) forever on an unmatched pack_id because tsleep(&hstat, PCATCH, sgnull, 0) at scsi_sg.c:790 has NO wakeup source anywhere in the tree (hstat is a stack local of that very invocation; sgdone only wakeup()s rdwr at :363 and &ccb_h.cbfcnp at :346/:367). Control read with a matching pack_id returned instantly (result=0); the unmatched read sat in wchan sgnull for the full 23 s observation (timo=0) and only a signal could break it. Aggravation discovered live: after SIGTERM the reader transitioned from S4/sgnull to a kernel-bound R4 spin and the guest livelocked within ~2 minutes (sshd dead, serial silent, qemu alive) on both attempts -- the indefinite block escalates to a whole-system livelock when the sleeper is signaled. RESTRICTEDROOT/operator gated (sgopen checks SYSCAP_RESTRICTEDROOT at :385; /dev/sg0 is 0600 root:operator on this guest, so the unprivileged actor is excluded).
No comments yet.