asr(4) unbounded polled waits: wait-forever ASR_resetIOP retry spin inside a critical section (asr_timeout-reachable), zero-timeout ASR_initOutBound reply poll reachable from the I2OUSRCMD ioctl, deadline-less ASR_queue_s poll, PCATCH-less ioctl wait
Summary
ASR_reset executes 'while (ASR_resetIOP(sc) == 0);' inside crit_enter() - a wedged IOP spins that CPU with interrupts blocked; ASR_initOutBound busy-loops a DMA'd status word with no timeout at all and is called directly from the I2OUSRCMD ioctl path; ASR_queue_s polls asr_intr without a deadline on every synchronous config path; ASR_queue_i waits in tsleep without PCATCH. The vendor changelog documents adapter lock-ups as an expected field condition. Transiently locked-up adapter -> CPU hang inside critical section (system-wide) or unkillable D-state ioctl threads. Bound the loops.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3825 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| asr_ioctl_poll.c | β | 2.5 KB | view raw | |
| build.sh | β | 102 B | view raw | |
| run.sh | β | 107 B | view raw | |
| build.log | β | 193 B | view raw | |
| run.log | β | 396 B | view raw | |
| run.2.log | β | 396 B | view raw | |
| env.txt | β | 323 B | view raw | |
| fix.diff | β | 1.8 KB | view raw | |
| README.md | β | 3.0 KB | β raw | |
| VERDICT.md | β | 2.5 KB | β raw | |
| verdict.json | β | 2.7 KB | view raw |
DF-3825 β asr(4) unbounded polled waits: a transiently wedged adapter hard-hangs the kernel (wait-forever spin inside a critical section, zero-timeout reply polls, unkillable ioctl sleep)
- File:
sys/dev/raid/asr/asr.c - Lines: 1253-1255 (
ASR_resetβwhile (ASR_resetIOP(sc) == 0);insidecrit_enter()), 1892 (ASR_initOutBoundβwhile (*Reply_Ptr < I2O_EXEC_OUTBOUND_INIT_REJECTED);with no timeout at all, directly reachable from theI2OUSRCMDioctl at :3211-3219), 727-729 (ASR_queue_sβ no-timeout interrupt poll), 3499-3523 (ASR_queue_iβ 1 stsleeploop withoutPCATCH) - Class: CWE-834/CWE-667 β unbounded busy-wait on untrusted device state β system hang / unkillable threads
- Reach: adapter lock-up (the V1.05 changelog documents the exact
failure mode this driver still hangs on) or, for the
initOutBoundleg, root viaioctl("/dev/asr0", I2OUSRCMD).
Analysis
ASR_reset()(asr.c:1226-1275) runs undercrit_enter(); if the IOP fails to re-arm it executes
c
1253: /* Wait Forever */
1254: while (ASR_resetIOP(sc) == 0);
an infinite spin β ASR_resetIOP itself polls MMIO/DMA with bounded
delays (asr.c:577-586) but the retry loop never gives up. Reached
from asr_timeout() (callout, asr.c:1297/1314) and
XPT_RESET_DEV/I2ORESETCMD. A spinning thread inside a critical
section blocks that CPU's interrupts β the whole system degrades to a
watchdog hang.
2. ASR_initOutBound() (asr.c:1854-1929):
c
1892: while (*Reply_Ptr < I2O_EXEC_OUTBOUND_INIT_REJECTED);
busy-loops on a DMA'd status word with no timeout, holding the
adapter's interrupts disabled. Besides boot/ASR_reset, this is
reachable verbatim from user space: ASR_queue_i() special-cases
function I2O_EXEC_OUTBOUND_INIT (asr.c:3211-3219) and calls it
directly.
3. ASR_queue_s() (asr.c:715-733) polls asr_intr(sc) until the
ccb completes β no deadline; a lost completion spins the caller
(kernel thread / ioctl) at full tilt. Every synchronous
configuration path (LCT, HRT, params, attach INQUIRY) funnels through
it.
4. ASR_queue_i() (asr.c:3499-3523) waits with tsleep(..., hz)
but no PCATCH and no bound β an unkillable D-state thread per
outstanding ioctl when the card stops replying (blink-LED check is
the only exit).
The changelog at asr.c:46-50 ("System locked up if the Adapter locked up") shows the vendor knew the class; these loops are the remaining instances.
Build / Run (guest trigger)
cc -O2 -Wall -o asr_ioctl_poll asr_ioctl_poll.c ./asr_ioctl_poll /dev/asr0
Expected on hardware: issuing I2O_EXEC_OUTBOUND_INIT while the IOP is
deaf wedges the caller in the zero-timeout loop.
Observed on the audit guest: open("/dev/asr0") β ENOENT β no I2O HBA
present (root and unprivileged, see run.log/run.2.log; env.txt).
Verdict
not_reproduced β hardware-gated (device absent); source-certain.
VERDICT β DF-3825
Status: not_reproduced (the audit guest has no DPT/Adaptec I2O
controller: /dev/asr0 β ENOENT for both root and an unprivileged
user; zero PCI devices with vendor 0x1044; the asr driver is compiled
into X86_64_GENERIC but never attaches). Source-level confidence:
certain.
The four unbounded waits
| # | site | loop | caller context | effect when adapter is deaf |
|---|---|---|---|---|
| 1 | asr.c:1254 (ASR_reset) |
while (ASR_resetIOP(sc) == 0); |
crit_enter() region; reached from asr_timeout callout (:1297/:1314), XPT_RESET_DEV (:2744), I2ORESETCMD ioctl (:3793) |
infinite spin with interrupts blocked on that CPU β system-wide hang/watchdog |
| 2 | asr.c:1892 (ASR_initOutBound) |
while (*Reply_Ptr < I2O_EXEC_OUTBOUND_INIT_REJECTED); β zero timeout |
boot, ASR_reset, and directly the I2OUSRCMD ioctl via ASR_queue_i (:3211-3219) |
unbounded busy loop on a DMA'd word with card interrupts masked |
| 3 | asr.c:727 (ASR_queue_s) |
while (status == CAM_REQ_INPROG) (void)asr_intr(sc); |
every synchronous config path (LCT/HRT/params/attach inquiry) | full-spin poll, no deadline |
| 4 | asr.c:3499-3523 (ASR_queue_i) |
tsleep(ccb, 0, "asr", hz) loop, no PCATCH |
I2OUSRCMD ioctl (root) |
unkillable D-state thread per wedged ioctl |
The driver's own changelog (asr.c:46-50, V1.05: "System locked up if the Adapter locked up ... The fail outstanding command discovery loop was flawed") establishes that transient IOP lock-ups are an expected field condition, not an attacker fiction.
Guest run
Trigger built on the guest (cc 8.3, build.log) and executed as root
and as user maxx: both exit(2) with open("/dev/asr0"): No such file or
directory β honest proof the surface is hardware-gated here
(run.log, run.2.log, env.txt).
Fix
fix.diff:
- bounds the ASR_resetIOP retry loop (60 attempts),
- gives the initOutBound reply poll a ~3 s deadline,
- gives ASR_queue_s a ~30 s budget after which the ccb fails with
CAM_CMD_TIMEOUT instead of spinning.
Fix validation
not_testable β exercising any leg requires an IOP that stops
replying; no asr hardware on the guest.
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
not_testableBoth baseline and patched behavior require a physically present, non-responding IOP; absent on the guest. fix.diff adds the three bounds.
fix.diff
Confirmed kernel references
Detail
Exploit chain
wedges: adapter stops replying during asr_timeout/ASR_reset -> infinite spin in crit section -> CPU interrupts blocked -> system hang; or root I2OUSRCMD with I2O_EXEC_OUTBOUND_INIT -> ASR_initOutBound zero-timeout poll -> unkillable thread.
Evidence (decisive lines)
run.log + run.2.log (trigger ENOENT as root and user); build.log (cc 8.3 on guest); env.txt (no vendor-0x1044 PCI devices); VERDICT.md table of the four sites; fix.diff (bounded retries/deadlines)
PoC changes
Trigger written fresh: I2OUSRCMD = _IO('D',76) per osd_unix.h, arg passed as pointer-to-pointer per asr.c:3789.
Verified recommended fix
Bound every polled wait: retry cap in ASR_reset, ~3s deadline in ASR_initOutBound, ~30s budget + CAM_CMD_TIMEOUT in ASR_queue_s (asr.c:727,1254,1892).
Verdict
Four unbounded waits on adapter state: ASR_reset spins 'while (ASR_resetIOP(sc) == 0);' inside crit_enter() (asr.c:1254) reachable from the asr_timeout callout and I2ORESETCMD; ASR_initOutBound busy-loops a DMA'd status word with zero timeout (asr.c:1892) and is directly reachable from the I2OUSRCMD ioctl (asr.c:3211-3219); ASR_queue_s polls asr_intr with no deadline (asr.c:727) on every synchronous config path; ASR_queue_i tsleeps without PCATCH (asr.c:3499-3523). A transiently wedged IOP - a field condition the driver changelog itself documents (asr.c:46-50) - therefore hard-hangs a CPU inside a critical section or strands unkillable D-state ioctl threads. Trigger built and run on the guest as root and unprivileged: open('/dev/asr0') -> ENOENT (no I2O HBA), so the path cannot be entered; source analysis certain.
No comments yet.