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

umcs7840_cfg_open/cfg_close chain of 28/4 sequential synchronous 500ms EP0 requests: evil device stretches every port open to ~14 s and blocks close(2) and unplug (hub event thread) uninterruptibly for ~16 s per cycle

Summary

cfg_open = 28 sequential sync EP0 requests (cfg_close adds 4), each 500ms timeout: a NAK-forever evil device (or a dialer-group user looping open/close on cuaU*) stretches every open to ~14s with close(2) and unplug waiting UNINTERRUPTIBLY against the same queue - the DF-5367 mechanism, umcs instance. Harness-proven: closer blocked 15702/15707/15700ms; 100ms-timeout fix caps the chain at ~3.2s. Bound-the-timeout fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5383 Β· 13 files
FileTypeDescriptionSize
umcs_cfgstall_harness.c β€” 6.8 KB view raw
Makefile β€” 104 B ↓ download
build.sh β€” 184 B view raw
run.sh β€” 149 B view raw
build.log β€” 7.1 KB view raw
fix_compile.log β€” 911 B view raw
run.log β€” 2.1 KB view raw
run.2.log β€” 1.2 KB view raw
env.txt β€” 188 B view raw
fix.diff β€” 694 B view raw
README.md β€” 3.2 KB ↓ raw
VERDICT.md β€” 4.7 KB ↓ raw
verdict.json β€” 4.2 KB view raw

DF-5383 β€” umcs7840 cfg_open/cfg_close chain of up to 28/4 synchronous 500 ms EP0 requests stalls close(2) and unplug (hub event thread) for ~16 s

What (finding in one line)

umcs7840_cfg_open() (sys/bus/u4b/serial/umcs.c:410-515) performs 28 synchronous EP0 control requests on first open (2 global-config at umcs.c:423,426 + 14 port init at umcs.c:431-483 + 6 baud-rate writes at umcs.c:1022-1046 via umcs7840_set_baudrate(9600) at umcs.c:487 + 6 tail requests at umcs.c:496-512), each via usbd_do_request_proc() (umcs.c:927/948/972/997) with UMCS7840_CTRL_TIMEOUT = 500 ms (umcs.h:33). umcs7840_cfg_close() adds 4 more (umcs.c:527-534). A USB device may NAK the data stage of a control transfer arbitrarily long, so an evil MCS7840 can make every request succeed in just-under-500 ms without tripping the error-abort paths (cfg_open/cfg_close only abort on hard errors). The whole chain runs on sc_super_ucom.sc_tq (usb_serial.c:322), and tty close is synchronous against that queue: ucom_close() β†’ ucom_queue_command(ucom_cfg_close) β†’ usb_proc_mwait() (usb_serial.c:671-672) cv_wait()s uninterruptibly (usb_process.c:385-393) behind the still-running cfg_open. Unplug racing a cfg task stalls the same way through ucom_detach() β†’ usb_proc_drain() (usb_serial.c:394) on the USB hub event thread β€” the DF-5367 (ufoma) mechanism, umcs instance. Worst case ~16 s uninterruptible stall per open/close cycle (28 + 4 requests Γ— 500 ms, + hz/10 pause at usb_serial.c:777).

Evidence class

No MCS7840 device model on the QEMU guest (as uipaq/ufoma) β†’ KLD harness built from production code: the real usb_proc_create/msignal/mwait machinery of ucom's taskqueue, with the per-request cost mirroring usbd_do_request_flags's WXFER sleep for a request the device stretches to just under the 500 ms timeout (usb_request.c:567-708).

Build

mkdir -p /usr/src/sys/bus/u4b/serial/hx5383
cp umcs_cfgstall_harness.c Makefile /usr/src/sys/bus/u4b/serial/hx5383/
sh build.sh          # in-tree kmod build, -Werror clean

Run

sh run.sh            # kldload ./umcscfgstall.ko; kldunload; dmesg

Expected output (decisive lines)

HX5383[A]: closer: queue cfg_close + usb_proc_mwait() (uninterruptible, usb_serial.c:671-672)
HX5383[A]: closer: unblocked  ELAPSED=15702 ms
HX5383[B]: closer: unblocked  ELAPSED=3228 ms

Phase A (500 ms/request, production constant) MUST show the closer blocked ~15.6-16 s. Phase B (fix mechanism from fix.diff: timeout bounded to 100 ms) MUST show ~3.2 s. run.log holds three loads: 15702/15707/15700 ms vs 3228/3232/3213 ms.

Fix validation

  • fix.diff (against umcs.h) β€” git apply --check passes; patched driver compiles -Werror-clean as a kmod on the guest (fix_compile.log, RC=0).
  • Harness Phase B shows the bounded-timeout mechanism: 15.7 s β†’ 3.2 s.

Artifacts

  • umcs_cfgstall_harness.c β€” the KLD harness (annotated with the umcs.c / usb_serial.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, env.txt
  • fix.diff β€” bound UMCS7840_CTRL_TIMEOUT to 100 ms with rationale
  • VERDICT.md, manifest.json, verdict.json
VERDICT.md
↓ download raw

DF-5383 VERDICT β€” umcs cfg_open/cfg_close sync-request chain stalls close(2)/detach ~16 s

Status: reproduced (mechanism level, production code) β€” impact: dos (evil USB device), no memory corruption.

  1. The chain. umcs7840_cfg_open() issues these synchronous control requests in order (each = one EP0 round trip through umcs7840_get/set[_UART]_reg_sync() β†’ usbd_do_request_proc()): umcs.c:423 (get CONTROL1, first open only), :426 (set CONTROL1, first open only), :431, :434, :437 (SP reset toggle), :441 (SCRATCH RS232), :445, :448 (CONTROLx RX-disable), :452 (IER=0), :456, :458 (FCR), :465 (LCR), :473 (MCR), :477, :480, :483 (FIFO reset), then umcs7840_set_baudrate(9600) at :487 β†’ umcs.c:1022, 1027, 1033, 1037, 1040, 1046 (clock select + DLL/DLM), then :496 (get LCR), :498 (set IER), :503, :506 (CONTROLx RX-enable), :510 (LSR), :512 (MSR) β€” 28 requests on first open, 26 later. umcs7840_cfg_close() adds 4 (umcs.c:527, 528, 531, 534).

  2. The timeout. Every request carries UMCS7840_CTRL_TIMEOUT = 500 ms (umcs.h:33). In usbd_do_request_flags() the requester sleeps in the "WXFER" loop (usb_request.c:642-644) until the transfer completes or the timeout fires (usb_request.c:700-704). USB permits a device to NAK the data stage indefinitely, so an evil device can make each request succeed just under the timeout β€” cfg_open's if (err) return; guards never fire, and the full chain costs ~28 Γ— 500 ms β‰ˆ 14 s (+ hz/10 trailing pause in ucom_cfg_open, usb_serial.c:777).

  3. Who blocks. All cfg tasks run on the per-adapter ucom taskqueue sc_super_ucom.sc_tq (created at usb_serial.c:322; tasks execute serialized, message staying head-of-queue during execution, usb_process.c:146-158). * close(2) of the tty is synchronous: ucom_close() β†’ ucom_queue_command(ucom_cfg_close) β†’ usb_proc_mwait() (usb_serial.c:671-672) cv_wait()s β€” uninterruptibly (usb_process.c:385-393) β€” until the cfg_close message drains, i.e. behind the running cfg_open (14 s) plus cfg_close itself (4 Γ— 500 ms β‰ˆ 2 s) β‰ˆ 16 s. * unplug racing an in-flight cfg task: umcs7840_detach() β†’ ucom_detach() β†’ usb_proc_drain() (usb_serial.c:394) β€” the USB hub event thread waits for the task to finish: same ~14-16 s per cycle (identical mechanism to DF-5367/ufoma, different driver and cause). * Reachability: open/close of /dev/ttyU* requires root/wheel (0600); /dev/cuaU* is 0660 uucp:dialer β€” a dialer-group user can drive the cycle; the pure evil-device variant needs no privileges at all, just insertion.

Why the harness is faithful

Production usb_proc_create/usb_proc_msignal/usb_proc_mwait are used verbatim (the same functions ucom's taskqueue and synchronous close use). Per-request cost mirrors a production "device stretches the data phase to just under the timeout": one lksleep(..., "WXFER", USB_MS_TO_TICKS(500)). Request counts (28 open + 4 close) are counted from the cited umcs.c lines. The closer thread reproduces usb_serial.c:671-672's mwait call pattern exactly.

Measured results (run.log, 3 module loads)

phase closer blocked
A baseline (500 ms/request = production) 15702 / 15707 / 15700 ms
B fix mechanism (fix.diff: 100 ms/request) 3228 / 3232 / 3213 ms

Expected β‰ˆ 27Γ—500 + 100 + 4Γ—500 = 15600 ms (A) and β‰ˆ 27Γ—100 + 100 + 4Γ—100 = 3200 ms (B). Match (the closer queues 100 ms into cfg_open).

Fix validation

  • fix.diff bounds UMCS7840_CTRL_TIMEOUT to 100 ms (one-line change in umcs.h with rationale): worst-case open+close chain drops from ~16 s to ~3.2 s while staying ≫ a healthy device's control latency (Β΅s-ms). Full remedy would additionally bound the whole cfg sequence; kept minimal here.
  • git apply --check passes; patched driver (umcs.c + patched umcs.h) compiles -Werror-clean as a kmod on the guest (fix_compile.log).
  • Harness Phase B: 15.7 s β†’ 3.2 s.
  • Scope caveat: mechanism + compile level (no MCS7840 device model on the guest), same sanctioned class as DF-5367.

Impact ceiling

Repeated ~16 s uninterruptible stalls of user threads closing the device and of the USB hub event thread during unplug, driven by an evil device (or a dialer-group user looping open/close); no memory corruption, no leak. Bounded per cycle, hence Low.

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); the patched driver (pristine umcs.c + patched umcs.h) compiles -Werror-clean as a kmod on the guest (fix_compile.log RC=0); harness Phase B with the bounded timeout measures closer block 15702-class ms -> 3228-class ms (5x reduction of the stall ceiling). Scope caveat: mechanism+compile level; end-to-end with a physical crafted device is not possible on this guest.

fix.diff (sha256 ab01b449...); fix_compile.log; run.log 'HX5383[B] ELAPSED=3228/3232/3213 ms'; VERDICT.md 'Fix validation' section
↓ fix.diffper-fix-DF-5383

Confirmed kernel references

Detail

Exploit chain

evil USB device (MCS7840 VID/PID) enumerates normally, then answers every vendor control request with the data stage NAK'd for ~499 ms -> open(/dev/ttyUx.0) queues cfg_open costing ~14 s on the ucom tq; close(2) (root/wheel on ttyU, dialer group on cuaU) blocks uninterruptibly ~16 s behind it via usb_proc_mwait; unplug mid-cycle wedges the USB hub event thread identically via usb_proc_drain. Repeatable per open/close cycle. No memory corruption.

Evidence (decisive lines)

run.log lines 'HX5383[A]: closer: unblocked  ELAPSED=15702/15707/15700 ms' vs 'HX5383[B]: ... 3228/3232/3213 ms'; VERDICT.md counts all 28+4 requests with umcs.c line cites and traces the close/unplug blocking path; build.log/fix_compile.log show -Werror-clean builds of harness and patched driver

PoC changes

No seed PoC existed; authored from scratch. Built clean on the first compile after applying the DF-5367 harness lessons (bsd.kmod.mk, opt_usb.h/device_if.h/bus_if.h SRCS).

Verified recommended fix

Bound UMCS7840_CTRL_TIMEOUT to 100 ms (one-line umcs.h change, full diff in fix.diff) so the 28+4-request open/close chain cannot exceed ~3.2 s; optionally also cap the whole cfg_open sequence

Verdict

Mechanism-level reproduction with production code: umcs7840_cfg_open() chains 28 synchronous EP0 control requests on first open (umcs.c:423-512 incl. 6 from umcs7840_set_baudrate at umcs.c:1022-1046) and umcs7840_cfg_close() 4 more (umcs.c:527-534), each with UMCS7840_CTRL_TIMEOUT=500ms (umcs.h:33). An evil device can NAK each control data stage to just under the timeout so every request succeeds slowly and no error-abort fires, making one open+close cost ~16s of ucom-taskqueue occupancy. A harness using the production usb_proc_create/msignal/mwait machinery with the production per-request cost shows the closer thread (ucom_close's synchronous usb_proc_mwait at usb_serial.c:671-672, uninterruptible cv_wait at usb_process.c:385-393) blocked 15702/15707/15700 ms across three runs; unplug racing a cfg task stalls the hub event thread the same way via ucom_detach's usb_proc_drain (usb_serial.c:394) -- the DF-5367 mechanism, umcs instance. Fix mechanism (timeout bounded to 100 ms, fix.diff) measures 3228/3232/3213 ms. End-to-end with a physical crafted MCS7840 is not possible on this guest (no device model), hence the KLD-harness evidence class.