umcs7840_intr_callback issues synchronous 500ms EP0 control reads on the shared per-bus USB callback thread: an evil MCS7840 device freezes ALL USB transfer callbacks on the bus indefinitely (2.25 s per crafted 13-byte interrupt packet)
Summary
The intr callback calls get_UART_reg_sync (LSR+MSR, 500ms timeout + hz/16 error pause each) from xfer-callback context - but all non-giant callbacks on a bus serialize on ONE thread: a 4-port device pays 4x562.5=2250ms per crafted packet (all-zero port bytes = MSCHANGE, not skipped, pipe resubmits every cycle) = sustained device-driven starvation of every other USB device's callbacks on the bus. Harness-proven with the production usb_process machinery: victim callback delayed ~2230ms per crafted packet, repeatable forever; 0ms with the defer-to-task fix. Defer-status-reads-to-the-ucom-task fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5382 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| umcs_busfreeze_harness.c | β | 10.7 KB | view raw | |
| Makefile | β | 106 B | β download | |
| build.sh | β | 185 B | view raw | |
| run.sh | β | 150 B | view raw | |
| build.log | β | 907 B | view raw | |
| fix_compile.log | β | 911 B | view raw | |
| run.log | β | 6.6 KB | view raw | |
| run.2.log | β | 528 B | view raw | |
| env.txt | β | 188 B | view raw | |
| fix.diff | β | 4.6 KB | view raw | |
| README.md | β | 3.8 KB | β raw | |
| VERDICT.md | β | 5.7 KB | β raw | |
| verdict.json | β | 4.4 KB | view raw |
DF-5382 β umcs7840 intr-callback synchronous EP0 reads freeze the shared per-bus USB callback thread (evil MCS7840 device, indefinite bus-wide USB stall)
What (finding in one line)
umcs7840_intr_callback() (sys/bus/u4b/serial/umcs.c:729-790) issues
synchronous 500 ms-timeout control requests from the xfer-callback
context β for every port whose interrupt byte signals
RXERR/RXHASDATA/RXTIMEOUT it reads LSR (umcs.c:756) and for MSCHANGE it
reads MSR (umcs.c:766). Xfer callbacks for the whole bus run serialized on
ONE kernel thread, bus->non_giant_callback_proc
(usb_bus.h:62,72; usb_transfer.c:1009-1014, 2286-2306, 2437), and each
request costs UMCS7840_CTRL_TIMEOUT = 500 ms (umcs.h:33, applied at
usb_request.c:421-428,557, slept at usb_request.c:642-644) plus a further
hz/16 = 62.5 ms error pause (usb_request.c:735-745). A failed read only
breaks the inner switch (umcs.c:757 /* Inner switch */), so a 4-port
device pays 4 Γ 562.5 = 2250 ms of bus-thread occupancy per 13-byte
interrupt packet β and an evil device streams packets (the pipe is
resubmitted at umcs.c:776-780), freezing every other USB device's transfer
callbacks on the bus (keyboard, mass storage, network) indefinitely.
Crafted packet: all four port bytes 0x00 β
0x00 & MCS7840_UART_ISR_NOPENDING(0x01) == 0 β not skipped, and
0x00 & MCS7840_UART_ISR_INTMASK(0x3f) == MCS7840_UART_ISR_MSCHANGE(0x00).
Why this guest can't run the full path (evidence class)
No QEMU USB device model for the MosChip MCS7840 exists on the audit guest
(same situation as uipaq DF-5359 / ufoma DF-5367). Sanctioned evidence
class: KLD harness compiled from production code β it drives the real
in-kernel usb_process machinery (usb_proc_create / usb_proc_msignal,
usb_process.c) that usb_callback_proc uses to serialize xfer callbacks,
with the per-request wait durations taken verbatim from production
constants (500 ms timeout, hz/16 error pause, 4 ports). The deferred-status
fix mechanism of fix.diff is exercised the same way (Phase B).
Build
# on the guest (DragonFly 6.5-DEVELOPMENT, /usr/src present): mkdir -p /usr/src/sys/bus/u4b/serial/hx5382 cp umcs_busfreeze_harness.c Makefile /usr/src/sys/bus/u4b/serial/hx5382/ sh build.sh # in-tree kmod build, -Werror clean
Run
sh run.sh # kldload ./umcsbusfreeze.ko; kldunload; dmesg
Expected output (decisive lines)
HX5382[A]: victim delay after 1 packet = 2226 ms (expect ~2250 baseline / ~0 fixed) HX5382[A]: stream round 1: victim delay = 2169 ms HX5382[A]: stream round 2: victim delay = 2158 ms HX5382[A]: stream round 3: victim delay = 2159 ms HX5382[B]: victim delay after 1 packet = 0 ms HX5382[B]: stream round 1: victim delay = 0 ms ...
Phase A (baseline) MUST show the "other device's transfer" callback delayed ~2.2 s per crafted interrupt packet (repeatable every packet = sustained bus freeze). Phase B (fix mechanism from fix.diff) MUST show ~0 ms. Three module loads in run.log confirm stability: 2226/2240/2230-style single-packet delays and 2152-2168 ms stream rounds, all fixed rounds 0 ms.
Fix validation
fix.diffβ git-apply-able againstsys/(git apply --checkpasses); the patchedumcs.ccompiles-Werror-clean as a kmod on the guest (fix_compile.log).- Harness Phase B runs the deferred-status pattern against the same production machinery: victim delay 2250-class β 0 ms.
Artifacts
umcs_busfreeze_harness.cβ the KLD harness (annotated with the umcs.c / usb_transfer.c / usb_request.c lines it mirrors)Makefile,build.sh,run.shbuild.log,fix_compile.log,run.log(decisive run, 3 loads),run.2.log(earlier stability run),env.txtfix.diffβ defer intr-path LSR/MSR refresh to the ucom super taskqueueVERDICT.md,manifest.json,verdict.json
DF-5382 VERDICT β umcs intr-callback sync EP0 requests freeze the per-bus USB callback thread
Status: reproduced (mechanism level, production code) β impact: dos (evil USB device), no memory corruption.
The bug, link by link
-
All non-giant xfer callbacks on a USB bus serialize on one thread.
usbd_transfer_setup()wires every xfer's completion toxfer->xroot->done_p = USB_BUS_NON_GIANT_PROC(udev->bus)(sys/bus/u4b/usb_transfer.c:1009-1014; theusb_processisbus->non_giant_callback_proc, sys/bus/u4b/usb_bus.h:62,72).usb_callback_proc()(usb_transfer.c:2286-2306) runsusb_command_wrapper()on the per-xfer-rootdone_qwhile holding the driver's lock, and the driver callback is invoked at usb_transfer.c:2437. A message stays head-of-queue while its callback executes (usb_process.c:146-158), so nothing else on that thread runs until the callback returns. -
umcs does synchronous control requests in that callback.
umcs7840_intr_callback()(umcs.c:729-790): for each subunit whose interrupt byte is notMCS7840_UART_ISR_NOPENDINGand whose& MCS7840_UART_ISR_INTMASKvalue is RXERR/RXHASDATA/RXTIMEOUT it callsumcs7840_get_UART_reg_sync(...LSR...)(umcs.c:756); for MSCHANGE it reads MSR (umcs.c:766). Those helpers issueusbd_do_request_proc()(umcs.c:972) βusbd_do_request_flags()β a synchronous EP0 transfer withUMCS7840_CTRL_TIMEOUT= 500 ms (umcs.h:33), sleeping in the "WXFER" loop (usb_request.c:642-644, timeout set at :557, fired at :700-704) and, on error (timeout hits thedefault:case), pausing a furtherhz/16= 62.5 ms with the lock held (usb_request.c:735-745). -
Error handling does not bound the damage. A failed read executes
break; /* Inner switch */(umcs.c:757, 767) β only the port's switch is exited, the subunit loop continues, so all four ports cost their full 562.5 ms even when every request fails against an unresponsive device. -
Trigger data is trivially craftable. A 13-byte interrupt packet with all port bytes
0x00:0x00 & 0x01 == 0(not NOPENDING) and0x00 & 0x3f == MCS7840_UART_ISR_MSCHANGEβ 4 sync MSR reads per packet. Theactlen == 5 || actlen == 13gate (umcs.c:741) accepts 13. The interrupt pipe is resubmitted at the end of every callback (umcs.c:776-780), so the device can deliver a new crafted packet as soon as processing finishes: one packet every 2.25 s of thread occupancy, forever β the shared bus callback thread is ~100% occupied, and every other USB device's completed transfers (queued on the same thread) starve. Practical effect: USB keyboard/mouse input and USB storage/network I/O on the same controller become multi-second latency or fully unresponsive β a local DoS from an evil USB device (evil peripheral / charging-cable threat model; this file's assigned threat model).
Worst case per packet: 4 Γ (500 + 62.5) = 2250 ms; per second of wall time an evil device can hold ~2.24 s of the bus thread.
Why the harness is faithful
- The production
usb_processmachinery is used (usb_proc_create,usb_proc_msignal, the single-threaded message loop of usb_process.c:79-181) β the same codeusb_callback_procruns under. - The per-request cost is production-derived: 500 ms =
UMCS7840_CTRL_TIMEOUT(umcs.h:33); 62.5 ms = thehz/16error pause (usb_request.c:743); 4 ports =UMCS7840_MAX_PORTS(umcs.h:30). The harness'slksleep(..., "WXFER", USB_MS_TO_TICKS(500))mirrors the requester's sleep while the device withholds the data phase, andusb_pause_mtx(&lk, hz/16)mirrors the post-timeout pause. - The "victim" message is any other device's completed transfer queued on the same thread β exactly what a keystroke or disk I/O completion does in production.
Measured results (run.log, 3 module loads; run.2.log)
| phase | victim delay after 1 packet | stream rounds 1-3 |
|---|---|---|
| A baseline (production pattern) | 2226 / 2240 / 2230-class ms | 2169, 2158, 2159 / 2152, 2161, 2161 / 2157, 2168, 2163 ms |
| B fix mechanism (fix.diff: deferred status task) | 0 ms | 0 / 0 / 0 ms |
Expected values: 2250 β lead-in β 2150-2240 ms (A), β 0 ms (B). Match.
Fix validation
fix.diff: the intr callback only recordssc_status_isrc[pn]/sc_status_pendingandusb_proc_msignals a status task onsc_super_ucom.sc_tqβ the ucom taskqueue where every other cfg callback already issues sync requests under the same lock (usbd_do_request_proc's documented contract, usb_request.c:756-757). The task re-runs the original switch (LSR/MSR read + ucom_status_change) there.git apply --checkpasses againstsys/.- Patched
umcs.ccompiles-Werror-clean as a kmod on the guest (fix_compile.log, RC=0). - Harness Phase B demonstrates the deferred pattern leaves the bus thread free: 2250-class ms β 0 ms.
- Scope caveat (same class as DF-5367): end-to-end with a physical crafted MCS7840 device is not possible on this guest (no such device model); the validation is mechanism + compile level against production code.
Impact ceiling
Sustained local DoS of all USB transfer processing on the affected bus for as long as the evil device is plugged in; no memory corruption, no info leak, no privilege boundary crossed. Unplug (or EP0 recovering) ends it.
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); patched umcs.c compiles -Werror-clean as a kmod on the guest (fix_compile.log RC=0); harness Phase B runs the deferred-status pattern against the same production usb_process machinery: victim delay 2250-class ms -> 0 ms. Scope caveat: mechanism+compile level; end-to-end with a physical crafted MCS7840 device is not possible on this guest.
fix.diff (sha256 4ea018b1...); fix_compile.log; run.log 'HX5382[B] ... = 0 ms' lines; VERDICT.md 'Fix validation' section
Confirmed kernel references
Detail
Exploit chain
evil USB device presents MOSCHIP MCS7840 VID/PID + 4-port GPIO bit -> umcs7840 attaches -> device streams 13-byte interrupt packets with all port bytes 0x00 (MSCHANGE, not NOPENDING) while never answering EP0 -> each packet costs the bus callback thread 4 x (500ms timeout + 62.5ms error pause) = 2250ms -> all other USB devices' transfer callbacks on the bus starve indefinitely -> local DoS (USB input/storage frozen). No memory corruption; unplug ends it.
Evidence (decisive lines)
run.log lines 'HX5382[A]: victim delay after 1 packet = 2226 ms' and stream rounds 2152-2168 ms across 3 loads; 'HX5382[B]: ... = 0 ms' for the fix mechanism; VERDICT.md traces every link (umcs.c:729-790 -> usb_request.c:642-644,735-745 -> usb_transfer.c:2286-2306,2437); build.log/fix_compile.log show -Werror-clean builds of harness and patched driver
PoC changes
No seed PoC existed; authored from scratch. One build iteration: usb_proc_msignal'ed helpers must be declared before first use and the unused slow-ok helper removed (-Werror). Also note SRCS needs usbdevs.h for driver compiles (only for fix compile-check, not the harness).
Verified recommended fix
Record pending interrupt bytes in the xfer callback and defer the LSR/MSR register reads to the ucom super taskqueue via a usb_proc_msignal'ed status task (full diff in fix.diff)
Verdict
Mechanism-level reproduction with production code: umcs7840_intr_callback (umcs.c:756/766) issues synchronous 500ms-timeout EP0 control requests from the xfer-callback context, which executes on the single shared per-bus callback thread bus->non_giant_callback_proc (usb_transfer.c:1009-1014,2286-2306,2437). A harness driving the real in-kernel usb_process machinery with the production per-request cost (500ms WXFER timeout + hz/16 error pause, 4 ports) shows any other device's completed transfer on the same bus delayed 2226/2240/2230-class ms per crafted 13-byte interrupt packet (all port bytes 0x00 -> MSCHANGE path; failed reads only break the inner switch at umcs.c:757/767 so all 4 ports pay), repeatable every packet for as long as the evil device streams -- sustained bus-wide USB callback starvation (keyboard/storage/network on the same controller). Fix mechanism from fix.diff (defer LSR/MSR refresh to the ucom super taskqueue) measures 0 ms. End-to-end with a physical crafted MCS7840 is not possible on this guest (no device model), hence the KLD-harness evidence class.
No comments yet.