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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5382 Β· 13 files
FileTypeDescriptionSize
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 against sys/ (git apply --check passes); the patched umcs.c compiles -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.sh
  • build.log, fix_compile.log, run.log (decisive run, 3 loads), run.2.log (earlier stability run), env.txt
  • fix.diff β€” defer intr-path LSR/MSR refresh to the ucom super taskqueue
  • VERDICT.md, manifest.json, verdict.json
VERDICT.md
↓ download raw

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.

  1. All non-giant xfer callbacks on a USB bus serialize on one thread. usbd_transfer_setup() wires every xfer's completion to xfer->xroot->done_p = USB_BUS_NON_GIANT_PROC(udev->bus) (sys/bus/u4b/usb_transfer.c:1009-1014; the usb_process is bus->non_giant_callback_proc, sys/bus/u4b/usb_bus.h:62,72). usb_callback_proc() (usb_transfer.c:2286-2306) runs usb_command_wrapper() on the per-xfer-root done_q while 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.

  2. umcs does synchronous control requests in that callback. umcs7840_intr_callback() (umcs.c:729-790): for each subunit whose interrupt byte is not MCS7840_UART_ISR_NOPENDING and whose & MCS7840_UART_ISR_INTMASK value is RXERR/RXHASDATA/RXTIMEOUT it calls umcs7840_get_UART_reg_sync(...LSR...) (umcs.c:756); for MSCHANGE it reads MSR (umcs.c:766). Those helpers issue usbd_do_request_proc() (umcs.c:972) β†’ usbd_do_request_flags() β€” a synchronous EP0 transfer with UMCS7840_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 the default: case), pausing a further hz/16 = 62.5 ms with the lock held (usb_request.c:735-745).

  3. 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.

  4. Trigger data is trivially craftable. A 13-byte interrupt packet with all port bytes 0x00: 0x00 & 0x01 == 0 (not NOPENDING) and 0x00 & 0x3f == MCS7840_UART_ISR_MSCHANGE β†’ 4 sync MSR reads per packet. The actlen == 5 || actlen == 13 gate (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_process machinery is used (usb_proc_create, usb_proc_msignal, the single-threaded message loop of usb_process.c:79-181) β€” the same code usb_callback_proc runs under.
  • The per-request cost is production-derived: 500 ms = UMCS7840_CTRL_TIMEOUT (umcs.h:33); 62.5 ms = the hz/16 error pause (usb_request.c:743); 4 ports = UMCS7840_MAX_PORTS (umcs.h:30). The harness's lksleep(..., "WXFER", USB_MS_TO_TICKS(500)) mirrors the requester's sleep while the device withholds the data phase, and usb_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 records sc_status_isrc[pn] / sc_status_pending and usb_proc_msignals a status task on sc_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 --check passes against sys/.
  • Patched umcs.c compiles -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

fixed
baseline reproduced→ patch + rebuild →patched reproduced

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

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.