ufoma mode-switch ACK waits are uninterruptible (1s link + 15s activate): unplug racing an open/close stalls ufoma_detach()'s usb_proc_drain on the USB hub event thread for up to ~16-18s per cycle
Summary
cfg_open/cfg_close end in cv_timedwait(hz / 15*hz) on the ucom cfg thread with no removal/drain abort - the sole wakeup is the vendor ACK notification, never signalled on unplug (intr xfer only cancelled): detach -> ucom_detach -> usb_proc_drain sleeps until the wait expires while the thread cannot observe up_gone, wedging the SYSTEM-WIDE USB hub event thread ~16-18s per cycle (1s LINK + 15s ACTIVATE + pause + 2x1s NAK timeouts). Evil MCPC device withholding ACKs + force-disconnect repeats at will; benign cable-yank racing open/close also triggers. Guest-verified via production-machinery KLD harness: drain blocked ELAPSED=13000/13005ms baseline, 0ms with the dying-flag+broadcast fix pattern. Slice-waits + wake-from-detach fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5367 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| ufoma_drain_harness.c | β | 5.4 KB | view raw | |
| Makefile | β | 100 B | β download | |
| build.sh | β | 173 B | view raw | |
| run.sh | β | 148 B | view raw | |
| build.log | β | 7.1 KB | view raw | |
| run.log | β | 781 B | view raw | |
| run.1.log | β | 781 B | view raw | |
| run.2.log | β | 725 B | view raw | |
| env.txt | β | 376 B | view raw | |
| fix.diff | β | 2.5 KB | view raw | |
| VERDICT.md | β | 4.8 KB | β raw | |
| README.md | β | 3.1 KB | β raw | |
| verdict.json | β | 4.5 KB | view raw |
DF-5367 β ufoma uninterruptible mode-switch ACK waits stall the USB hub event thread at detach (up to ~16-18 s)
What (finding in one line)
ufoma_cfg_open() / ufoma_cfg_close() block in cv_timedwait(&sc->sc_cv,
&sc->sc_lock, β¦) for up to 1 s (ufoma_cfg_link_state, sys/bus/u4b/serial/ufoma.c:527)
plus 15 s (ufoma_cfg_activate_state, ufoma.c:549-550 β UFOMA_MAX_TIMEOUT),
with no abort when the device is removed or the ucom process is draining.
The only wakeup source is the ACK notification handled in
ufoma_intr_callback() (ufoma.c:689-697); on unplug the interrupt xfer is
merely cancelled (ufoma.c:601,647,759) and nobody signals sc_cv.
When unplug races an in-flight open/close cfg task, ufoma_detach()
(ufoma.c:476) β ucom_detach() β usb_proc_drain() (usb_serial.c:394)
blocks the USB hub event thread for the remainder of the wait β
measured 13.005 s for an unplug 2 s into the 15 s wait β delaying all
USB event processing system-wide, repeatable per insertion cycle.
Why this guest can't run the full path (evidence class)
ufoma probes on USB interface class UICLASS_CDC (0x02) / subclass
UISUBCLASS_MCPC (0x88) (ufoma.c:114,314-317). The audit QEMU guest's
usb-serial attaches but binds uftdi (proven in DF-5359) β no MCPC device
can enumerate. Per the sanctioned evidence classes, verification is a
KLD harness compiled from production code: it drives the same
in-kernel usb_process machinery (usb_proc_create / usb_proc_msignal
/ usb_proc_drain) that ufoma_detach()'s ucom_detach() goes through,
with a cfg task performing exactly ufoma.c:549's cv_timedwait(15*hz).
Build
# on the guest (DragonFly 6.5-DEVELOPMENT, /usr/src present): mkdir -p /usr/src/sys/bus/u4b/serial/hx5367 cp ufoma_drain_harness.c Makefile /usr/src/sys/bus/u4b/serial/hx5367/ sh build.sh # in-tree kmod build, -Werror clean
Run
sh run.sh # kldload ./ufoharness.ko; kldunload; dmesg
Expected output (decisive lines)
HX: DF-5367 ufoma detach-stall harness, hz=100 HX[A] cfg-task: enter t=1791032834, cv_timedwait(15*hz), no abort on gone HX[A] unplug done, usb_proc_drain() begin t=1791032836 HX[A] cfg-task: leave t=1791032849 HX[A] usb_proc_drain() done t=1791032849 ELAPSED=13000 ms ... HX[B] cfg-task(fixed): enter t=1791032849, 1s slices + dying HX[B] unplug done, usb_proc_drain() begin t=1791032851 HX[B] usb_proc_drain() done t=1791032851 ELAPSED=0 ms ...
Phase A (baseline) MUST show ELAPSED β (15 β lead-in) seconds.
Phase B (fix mechanism from fix.diff) MUST show ELAPSED β 0 ms.
Run 2 confirmed stability: ELAPSED=13005 ms / 0 ms.
Artifacts
ufoma_drain_harness.cβ the KLD harness (annotated with the ufoma.c lines it mirrors)Makefile,build.sh,run.shbuild.log,run.log,run.1.log,run.2.log,env.txtfix.diffβ git-apply-able againstsys/(verified:git apply --checkpasses; the patched driver also compiles-Werror-clean as a kmod on the guest)VERDICT.md,manifest.json,verdict.json
VERDICT β DF-5367 (ufoma detach-stall)
status: reproduced (mechanism-level, production usb_process code) β impact: dos
Reproduced? Yes β how and why
The full end-to-end chain needs a crafted MCPC USB device (interface class
0x02 / subclass 0x88). This guest cannot attach one (usb-serial binds
uftdi, established in DF-5359), so per the sanctioned evidence classes the
PoC is a KLD harness built from production code that instantiates the
exact kernel machinery the finding is about:
usb_proc_create()+usb_proc_msignal()β the same ucom cfg-task process (sc_tq) thatucom_attach()creates (usb_serial.c:322) anducom_queue_command()feeds (usb_serial.c:635-659);- a cfg task whose body is ufoma.c:549-550 verbatim:
cv_timedwait(&sc->sc_cv, &sc->sc_lock, UFOMA_MAX_TIMEOUT * hz)with nousb_proc_is_gone()/ device-removal abort β exactly whatufoma_cfg_open()βufoma_cfg_link_state()/ufoma_cfg_activate_state()(ufoma.c:837-842) andufoma_cfg_close()βufoma_cfg_activate_state()(ufoma.c:846-851) execute on the ucom cfg thread; usb_proc_drain()β the exact drainucom_detach()calls at usb_serial.c:394 on theufoma_detach()path (ufoma.c:480).
Measured (run.log, run.2.log):
| phase | expectation | run 1 | run 2 |
|---|---|---|---|
| A baseline: drain after "unplug" 2 s into the 15 s wait | β 13 s | 13000 ms | 13005 ms |
B fix mechanism (dying flag + cv_broadcast before drain) |
β 0 s | 0 ms | 0 ms |
Why the mechanism is exact (kernel refs):
usb_proc_drain()setsup_goneand sleeps onup_cvuntil the process thread exits (usb_process.c:424-442);- the thread (usb_process.c:146-170) executes the callback to completion
before it can ever re-check
up_goneβ a callback blocked in a 15 scv_timedwaitdelays the drain by the full remainder; - the wakeup never comes from the driver side: the only
cv_signal(&sc_cv)is inufoma_intr_callback()(ufoma.c:696) for the vendor ACK notification; on unplug the intr xfer completesUSB_ERR_CANCELLEDand the callback simply returns (ufoma.c:601-602, 758-764).
So: unplug (or kldunload, or shutdown) racing an open/close within the
~2-18 s cfg-task window stalls ufoma_detach() β which runs on the USB hub
event thread β for the remainder of the waits (1 s link + 15 s activate +
0.1 s pause, plus up to 2Γ1 s control-request timeouts on a NAKing device).
An evil device that withholds the ACK and force-disconnects on its own
timing (a device can drop D+/Dβ at will) makes this deterministic per
insertion cycle; repeatable cycles yield sustained USB event-thread
starvation. Worst-case single-event block measured/derived: ~16-18 s.
Exploit chain
Not applicable (no memory corruption). Escalation ceiling: sustained
system-wide USB hotplug/event denial plus a 16-18 s wedge of the hub event
thread per cycle, triggered by an evil (or merely flaky) MCPC device plus
any local open()/close() of the cuaU* node (any user; the callout nodes
are world-accessible by default) β or simply an unplug racing a user's
open()/close().
Fix validation
fix.diff(authored after verification): slices the ACK waits into 1 s pieces with asc_dyingabort, andufoma_detach()setssc_dyingandcv_broadcast(&sc_cv)beforeucom_detach(), so drain completes immediately.git apply --checkpasses against the read-onlysys/tree.- The patched driver compiles -Werror-clean as a kmod on the guest (build output in the run transcript).
- Harness Phase B runs the patched pattern against the same production drain machinery: 13000 ms β 0 ms.
- Full end-to-end patched-kernel validation is not possible on this guest (no MCPC device can attach; a rebuilt kernel would not change the harness, which exercises usb_process, not ufoma). Hence fix confidence is mechanism+compile level, not device level.
Honesty notes
- The finding is bounded-time (β€ ~18 s per event) and requires either an evil/updated device or an unplug racing a tty open/close β Low severity, same weight class as DF-5359 (uipaq explore stall, 12.8 s, Low).
- What was executed: production
usb_process/usb_procdrain path with ufoma's exact wait shape (2 runs). What was traced but not executed: the device-side trigger (MCPC descriptor + ACK withholding + forced disconnect) β every link is cited in ufoma.c / usb_serial.c / usb_process.c and is deterministic scheduling, not speculation.
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
fixedfix.diff is git-apply-able against sys/ (git apply --check passes), the patched ufoma.c compiles -Werror-clean as a kmod on the guest, and harness Phase B runs the patched wait/detach pattern against the same production usb_proc drain machinery: drain 13000 ms -> 0 ms. Scope caveat: validation is mechanism+compile level; end-to-end with a physical crafted MCPC device is not possible on this guest (no such device can attach, DF-5359), and a rebuilt kernel would not alter the harness (it exercises usb_process, not ufoma).
fix.diff (sha256 55437f6b...); run.log HX[B] ELAPSED=0 ms lines; compile of patched driver in run transcript; VERDICT.md 'Fix validation' section
Confirmed kernel references
- sys/bus/u4b/serial/ufoma.c:141
- sys/bus/u4b/serial/ufoma.c:512-532
- sys/bus/u4b/serial/ufoma.c:534-554
- sys/bus/u4b/serial/ufoma.c:689-697
- sys/bus/u4b/serial/ufoma.c:476-491
- sys/bus/u4b/serial/ufoma.c:828-851
- sys/bus/u4b/serial/usb_serial.c:394
- sys/bus/u4b/serial/usb_serial.h:220-221
- sys/bus/u4b/usb_process.c:146-174
- sys/bus/u4b/usb_process.c:406-452
Detail
Evidence (decisive lines)
run.log / run.2.log lines 'HX[A] usb_proc_drain() done ... ELAPSED=13000 ms' (baseline) and 'HX[B] ... ELAPSED=0 ms' (fix); VERDICT.md traces every link: ufoma.c:527,549-550 waits -> only wakeup is intr ACK at ufoma.c:696, cancelled silently on unplug at ufoma.c:601/759 -> ucom_detach drain at usb_serial.c:394 -> usb_process.c:424-442 waits for thread exit -> usb_process.c:146-170 thread cannot observe up_gone inside the callback. build.log shows -Werror-clean production compile.
PoC changes
Original PoC sketch did not exist; authored from scratch. Three build iterations were needed: (1) Makefile must include
Verified recommended fix
Slice the ACK waits into 1 s cv_timedwait pieces with a sc_dying abort, and have ufoma_detach() set sc_dying + cv_broadcast(&sc_cv) before ucom_detach() (full diff in fix.diff)
Verdict
Mechanism-level reproduction with production code: a ucom cfg task executing ufoma.c:549-550's exact pattern (cv_timedwait(&sc_cv,&sc_lock,15*hz), no abort on device-gone) delays usb_proc_drain() -- the drain ucom_detach() performs at usb_serial.c:394 on the ufoma_detach() path -- by the full remaining timeout. Measured 13000 ms / 13005 ms across two runs for an unplug 2 s into the 15 s wait; fix mechanism (sc_dying + cv_broadcast before drain) measured 0 ms. End-to-end device trigger (crafted MCPC device, class 0x02/subclass 0x88, withholding the vendor ACK and force-disconnecting) is deterministic per the cited code but cannot attach on this guest (usb-serial binds uftdi, DF-5359), hence the KLD-harness evidence class. Impact ceiling: 16-18 s USB-hub-event-thread wedge per open/close-racing-unplug event, repeatable per insertion cycle by an evil device; no memory corruption.
No comments yet.