run_drain_fifo() unbounded: loop exit depends on device-controlled VLD bit and run_read() failure value 0xffffffff has that bit set - permanent task wedge and device_detach hang
Summary
for(;;) ignores run_read's return; failure sets *val=0xffffffff whose bit0 IS RT2860_TXQ_VLD -> every failed read keeps the loop alive (wcid=0xff continue too), each iteration up to 10x250ms retries. Unplug/wedge or hostile always-VLD device spins the net80211 cmdq task forever and run_detach's draintask blocks device_detach indefinitely. Guest-evaluated exact loop conditions: never terminates in both scenarios. Bound-16 + break-on-error fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5248 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.2 KB | β raw | |
| VERDICT.md | β | 2.9 KB | β raw | |
| drain_fifo_loop.c | β | 3.2 KB | view raw | |
| build.sh | β | 108 B | view raw | |
| run.sh | β | 101 B | view raw | |
| build.log | β | 377 B | view raw | |
| run.log | β | 228 B | view raw | |
| env.txt | β | 199 B | view raw | |
| fix.diff | β | 780 B | view raw | |
| manifest.json | β | 725 B | view raw | |
| verdict.json | β | 2.7 KB | view raw |
DF-5248 β run_drain_fifo() never terminates on failed/lying TX-stat reads
What
run_drain_fifo() (if_run.c:2503-2563) polls the device TX-status FIFO
in an unbounded for(;;), ignores the return value of run_read()
(2515), and run_read() returns 0xffffffff on error (1254) which has
RT2860_TXQ_VLD (bit 0, if_runreg.h:664) set. Result: the loop can
never exit while USB control reads fail (unplugged/wedged device) or
while a hostile device always reports a valid FIFO entry. Each iteration
costs up to 10 Γ 250 ms of control-request retries (1223-1242), wedging
the net80211 task thread; run_detach()'s ieee80211_draintask()
(879-880) then hangs device_detach() forever.
Repro (logic-level, hardware-gated)
build: cc -O2 -o drain_fifo_loop drain_fifo_loop.c (build.sh) run: ./drain_fifo_loop (run.sh)
Both scenarios (persistent read error; hostile always-VLD device) hit the 1e6-iteration cap without terminating β the kernel has no cap.
Verdict
inconclusive (no run(4) hardware attachable to the QEMU guest),
confidence certain on the control-flow logic itself.
DF-5248 β run_drain_fifo() unbounded loop wedges the net80211 task and hangs detach
sys/bus/u4b/wlan/if_run.c:2503-2563
Root cause (path:line)
for (;;) { /* 2513 */
run_read(sc, RT2860_TX_STAT_FIFO, &stat); /* 2515 - return ignored */
if (!(stat & RT2860_TXQ_VLD)) /* 2517 */
break;
wcid = (stat >> RT2860_TXQ_WCID_SHIFT) & 0xff; /* 2520 */
if (!(stat & RT2860_TXQ_ACKREQ) || wcid > RT2870_WCID_MAX ||
wcid == 0) /* 2523 */
continue; /* 2525 */
...
}
RT2860_TXQ_VLD == (1 << 0)(if_runreg.h:664).run_read()on USB failure sets*val = 0xffffffff(if_run.c:1250-1255).0xffffffffhas bit 0 set andwcid == 0xff(βwcid > RT2870_WCID_MAX, 2523-2525continue).
So every failing control read keeps the loop alive: the loop has no
iteration bound and no error exit. A device that stops answering
control transfers (unplugged mid-hostap-TX, wedged, or hostile
physical-USB device that simply stalls RT2870_READ_REGION_1 or always
returns VLD|ACKREQ data) makes run_drain_fifo() spin forever. Each
run_read() takes up to 10 Γ 250 ms of retries in run_do_request
(if_run.c:1223-1242), so the loop is not even a cheap spin: the cmdq
task thread is lost forever.
Consequences:
run_ratectl_cb()βrun_drain_fifo()(2490) β the ratectl task never returns; the 1 s callout state machine is dead.run_tx()FIFO-drain deferral (3427-3429) queues onto the same wedged task.run_detach()callsieee80211_draintask(ic, &sc->cmdq_task)/ratectl_task(879-880) βtaskqueue_drain()blocks forever βdevice_detachnever returns β the USB exploration thread hangs; replugging the adapter is impossible without a reboot. (Persistent kernel DoS from a device unplug at the wrong moment or from a hostile device.)
Simulated verification
drain_fifo_loop.c reproduces both non-termination conditions with the
exact kernel expressions; both scenarios run to the artificial
1,000,000-iteration cap without terminating (see run.log).
Dynamic in-kernel repro requires a run(4) device (hardware-gated on this
QEMU guest) β inconclusive, confidence certain on the logic
(the loop conditions are decidable statically and were executed
faithfully in the harness).
Fix
Bound the loop (FIFO is 16 deep) and break on read error β see fix.diff
(applies cleanly with git apply).
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_testablefix.diff authored and verified with 'git apply --check' against the pristine sys/ tree, but cannot be kernel-tested: the QEMU guest has no USB controller and no run(4) device model exists, so neither baseline nor patched kernel behavior is exercisable. Hardware-gated: not_testable.
fix.diff (git-apply clean); VERDICT.md
Confirmed kernel references
Detail
Exploit chain
hostile/unplugged run(4) device during hostap TX-stat drain -> infinite bounded-cost loop in task thread -> net80211 task wedge + permanent device_detach hang (persistent kernel DoS until reboot)
Evidence (decisive lines)
findings/poc/DF-5248/run.log: both scenarios 'NEVER TERMINATES after 1000000 iterations'; if_runreg.h:664 RT2860_TXQ_VLD=(1<<0); if_run.c:1254 error value 0xffffffff
PoC changes
Harness written from scratch (no seed)
Verified recommended fix
Bound the FIFO drain loop (hardware FIFO is 16 deep) and break on run_read() error
Verdict
run_drain_fifo() (if_run.c:2513-2559) has an unbounded for(;;) whose only exit is !(stat & RT2860_TXQ_VLD), but the run_read() return value is ignored (2515) and run_read() returns 0xffffffff on USB failure (if_run.c:1254), which has VLD (bit 0) set. A device that stops answering control transfers or a hostile always-VLD device makes the loop eternal; each iteration costs up to 10x250ms in run_do_request retries (1223-1242), wedging the net80211 cmdq task and hanging run_detach()'s ieee80211_draintask (879-880) forever - device_detach never returns. Both non-termination conditions demonstrated by drain_fifo_loop.c with the exact kernel expressions (1e6-iteration cap never reached an exit). In-kernel repro hardware-gated -> inconclusive; control-flow logic is certain.
No comments yet.