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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5248 Β· 11 files
FileTypeDescriptionSize
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

sys/bus/u4b/wlan/if_run.c

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.

VERDICT.md
↓ download raw

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). 0xffffffff has bit 0 set and wcid == 0xff (β†’ wcid > RT2870_WCID_MAX, 2523-2525 continue).

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:

  1. run_ratectl_cb() β†’ run_drain_fifo() (2490) β€” the ratectl task never returns; the 1 s callout state machine is dead.
  2. run_tx() FIFO-drain deferral (3427-3429) queues onto the same wedged task.
  3. run_detach() calls ieee80211_draintask(ic, &sc->cmdq_task) / ratectl_task (879-880) β†’ taskqueue_drain() blocks forever β†’ device_detach never 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_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-5248

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.