umoscom cfg callbacks chain 14 sequential 1000 ms-timeout EP0 vendor requests with discarded errors on first open (4 param + 5 open + 3 line-state + 2 status): evil MCS7703 stretches one open(2) to ~14.2 s of uninterruptible ucom-taskqueue occupancy, blocks close(2) SIGKILL-immune behind it, stalls the USB hub event thread on unplug; every later tcsetattr adds ~4.1 s (user-amplifiable)
Summary
Every cfg step is a sync 1000ms EP0 request with the error discarded - no chain aborts: the ucom first-open path queues 14 sequential requests + 2 hz/10 pauses (~14.2s for a silent device; the largest 1000ms-family chain measured this pass); later tcsetattrs add ~4.1s each via the double-buffered param task; close blocks SIGKILL-immune and unplug pins the hub event thread. Harness: 13922ms first-open+close, 17923ms with one tcsetattr; abort-on-first-error 4109ms; abort+250ms-bound 1229ms. Abort + bounded-timeout fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5456 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.2 KB | β raw | |
| VERDICT.md | β | 4.5 KB | β raw | |
| mo5456stall_harness.c | β | 12.5 KB | view raw | |
| gen_fixes.py | β | 6.2 KB | view raw | |
| Makefile | β | 105 B | β download | |
| build.sh | β | 144 B | view raw | |
| run.sh | β | 188 B | view raw | |
| build.log | β | 7.1 KB | view raw | |
| run.log | β | 3.8 KB | view raw | |
| env.txt | β | 634 B | view raw | |
| fix.diff | β | 4.7 KB | view raw | |
| fix_compile.log | β | 2.5 KB | view raw | |
| umoscomcfgstall.ko | β | 11.3 KB | β download | |
| manifest.json | β | 2.4 KB | view raw | |
| verdict.json | β | 5.0 KB | view raw |
DF-5456 β umoscom(4) cfg callbacks chain 14 sequential 1000 ms-timeout EP0 vendor requests with discarded errors on first open (4 param + 5 open + 3 line-state + 2 status): evil MCS7703 stretches one open(2) to ~14.2 s of uninterruptible ucom-taskqueue occupancy, blocks close(2) SIGKILL-immune behind it, stalls the USB hub event thread on unplug, and every later tcsetattr adds ~4.1 s (user-amplifiable)
Production file: sys/bus/u4b/serial/umoscom.c. Evidence class:
KLD-harness-with-production-code (no MCS7703 device model on the QEMU
guest; same rig family as DF-5425 uslcom / DF-5424 uftdi / DF-5449 uvscom).
What the harness is
mo5456stall_harness.c drives the REAL in-kernel u4b usb_process
machinery (usb_proc_create/msignal/mwait/drain) β the same
taskqueue ucom's cfg callbacks run on β with umoscom's exact production
request counts and the production 1000 ms per-request timeout. Each
"request" is one lksleep("WXFER", 1000ms), the exact production cost of
an EP0 data/status stage an evil device never answers
(usb_request.c:642-644, 699-704).
Build
sh build.sh # /usr/src/sys/bus/u4b/serial/mo5456 + make (in-tree kmod)
Run
sh run.sh # kldload umoscomcfgstall.ko (all phases at MOD_LOAD); dmesg
Expected (decisive) output
MO5456[A]: closer: unblocked ELAPSED=13922 ms first-open chain + close(2) MO5456[2]: closer: unblocked ELAPSED=17923 ms + 1 extra user tcsetattr MO5456[B]: closer: unblocked ELAPSED=4109 ms fix #1: abort on first error MO5456[C]: closer: unblocked ELAPSED=1229 ms fix #1+#2: abort + 250 ms
Production chain accounting (first open, evil silent device):
| task | queued at | requests | umoscom.c |
|---|---|---|---|
| cfg_param | usb_serial.c:842 | 4 Γ write | :454,:457,:460,:492 (+hz/10 pause :1636) |
| cfg_open | usb_serial.c:865 | 5 Γ write | :368,:371,:377,:380,:383 (+hz/10 pause :777) |
| line-state | usb_serial.c:874-878 | 3 Γ write | :415 (DTR), :428 (RTS), :402 (BREAK-off) |
| status | usb_serial.c:880 | 2 Γ read | :506 (LSR), :507 (MSR) |
| total | 14 Γ 1000 ms + 2 Γ 100 ms | β 14.2 s |
Every later tcsetattr re-queues cfg_param (double-buffered,
usb_serial.c:1691-1693): +4.1 s each, unprivileged-user amplification.
close(2) is synchronous against the queue (ucom_queue_command β
usb_proc_mwait, usb_serial.c:671-672 + 955-957, uninterruptible
cv_wait per usb_process.c:385-393) β umoscom_cfg_close itself is a
no-op (umoscom.c:386-390), so the closer purely waits out what is queued
in front of it. Unplug mid-cycle stalls the USB hub event thread via
ucom_detach() β usb_proc_drain() (usb_serial.c:394). A
slow-but-answer-at-999 ms evil device makes every request succeed
with no error at all β same occupancy, no error to propagate.
Fix
fix.diff β bound the cfg-path control timeout (1000 β 250 ms via
UMOSCOM_CFG_TIMEOUT), make umoscom_cfg_write() return the
usb_error_t, abort the cfg_open/cfg_param chains on the first failed
request (divisor-latch safety preserved: the final LCR write still closes
the latch whenever execution gets past it). Runtime mechanism validated
by phases B/C; patched umoscom.ko builds -Werror-clean
(fix_compile.log, RC=0).
DF-5456 VERDICT β reproduced (impact: dos)
Finding
Every umoscom cfg-callback step is a synchronous EP0 vendor control
request via ucom_cfg_do_request() (usb_serial.h:220-221 β
usbd_do_request_proc, 1000 ms timeout) whose usb_error_t result is
discarded at every call site β umoscom_cfg_write() returns void and
umoscom_cfg_read() returns only the (possibly uninitialized, DF-5455)
byte (umoscom.c:524-557). No chain step ever aborts on device failure.
Chain accounting for ONE first open of the tty (all tasks queued on the
per-adapter sc_super_ucom.sc_tq by ucom_open(), usb_serial.c:801-903):
ucom_param()at :842 βumoscom_cfg_param()β 4 writes (umoscom.c:454 LCR divlatch-enable, :457 BAUDLO, :460 BAUDHI, :492 LCR format) + hz/10 pause (usb_serial.c:1636);ucom_cfg_openat :865 βumoscom_cfg_open()β 5 writes (:368 FIFO purge, :371 FIFO enable, :377 INT enable, :380 magic 0x01, :383 magic 0x00) + hz/10 pause (usb_serial.c:777);- line-state task at :874-878 (DTR|RTS + ring-off + break-off coalesced by usb_serial.c:1384-1420) β 3 writes (umoscom.c:415 set_dtr, :428 set_rts, :402 set_break);
ucom_status_change()at :880 βumoscom_cfg_get_status()β 2 reads (umoscom.c:506 LSR, :507 MSR).
= 14 sequential 1000 ms-timeout requests + 2 Γ hz/10 pauses β 14.2 s of uninterruptible taskqueue occupancy for one open(2) of an evil/silent MCS7703 β the largest 1000 ms-family chain measured this pass (uslcom 5, uftdi 5, uvscom 5 requests).
Amplifiers:
- every later
tcsetattrof the open tty re-queues cfg_param (double-buffered, usb_serial.c:1691-1693): +4.1 s each, unprivileged local user; close(2)is synchronous against the queue (usb_serial.c:671-672 + 955-957, uninterruptible usb_process.c:385-393) whileumoscom_cfg_close()itself is a no-op (umoscom.c:386-390) β the closer purely waits out whatever is in front of it;- unplug mid-cycle pins the USB hub event thread via
ucom_detach()βusb_proc_drain()(usb_serial.c:394) β the DF-5367 mechanism; - a device that answers each request at just under 1000 ms makes every request succeed β same occupancy, nothing to propagate even if errors were checked (only the bounded timeout helps there).
Reproduction (KLD harness, guest 6.5-DEVELOPMENT, hz=100)
Real usb_process taskqueue, production request counts, production
1000 ms timeout, per-request cost = one lksleep("WXFER", 1000 ms)
(the exact traced cost of a data stage the device never answers):
| phase | meaning | measured |
|---|---|---|
| A | first-open chain + synchronous close | 13922 ms |
| 2 | + 1 extra user tcsetattr behind it | 17923 ms |
| B | fix mechanism #1 (abort chain on first error) | 4109 ms |
| C | fix #1+#2 (abort + 250 ms bound) | 1229 ms |
(run.log; full per-task traces with request accounting in the same
log.) Matches the predicted 14.2/18.3/4.4/1.2 s minus the closer's
100 ms head start. Phase C is also the fix-mechanism validation: the
family fix cuts the worst case from 14.2 s to ~1.2 s.
Impact ceiling
Evil-USB (or wedged-device) precondition: insertion of a crafted MOSCHIP 0x9710:0x7703 adapter, or any real MCS7703 that stops answering EP0. Then: per-open ~14 s uninterruptible occupancy of the adapter's ucom taskqueue, SIGKILL-immune blocked closers, hub event thread stalls on unplug, unprivileged-user-tcsetattr amplification. Severity: Low (family precedent DF-5421/5424/5425/5449; below uplcom's 70 s Medium DF-5407), base:hw bucket.
Fix validation
fix.diff(UMOSCOM_CFG_TIMEOUT=250, error-propagatingumoscom_cfg_write(), chain aborts on first failure in cfg_open/cfg_param with divisor-latch safety preserved): patched productionumoscom.kobuilds -Werror-clean on the guest (fix_compile.log, RC=0, 11 hunks applied).- Runtime mechanism: harness phases B (4109 ms) and C (1229 ms) vs
baseline A (13922 ms) β
fix_baseline_reproduced=1,fix_patched_reproduced=1(mechanism-level, the family-standard validation for this class: the request-level behavior change is exercised directly; no full patched-kernel boot is required to observe a per-request timeout bound).
Not applied to sys/ (read-only audit target).
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
fixedMechanism-level validation per family standard: harness phase B (abort-on-first-error) cuts the measured closer block from 13922 ms to 4109 ms and phase C (abort + 250 ms bound) to 1229 ms; the 11-hunk fix.diff (error-propagating cfg_write, chain aborts with divisor-latch safety, bounded timeout) compiles the patched production umoscom.ko -Werror-clean RC=0 on the guest (fix_compile.log). A full patched-kernel boot is not required to observe a per-request timeout bound + early-abort behavior that the harness exercises directly.
fix.diff (sha256 a967419288b03465a9174dacc32a35db32056a6fa061ec3e99865d7d5ed4beb4); fix_compile.log (RC=0); run.log phases B/C ELAPSED lines.
Confirmed kernel references
- sys/bus/u4b/serial/umoscom.c:441-493
- sys/bus/u4b/serial/umoscom.c:361-384
- sys/bus/u4b/serial/umoscom.c:386-390
- sys/bus/u4b/serial/umoscom.c:495-522
- sys/bus/u4b/serial/umoscom.c:524-557
- sys/bus/u4b/serial/usb_serial.c:801-903
- sys/bus/u4b/serial/usb_serial.c:842
- sys/bus/u4b/serial/usb_serial.c:865-880
- sys/bus/u4b/serial/usb_serial.c:671-672
- sys/bus/u4b/serial/usb_serial.c:1691-1693
- sys/bus/u4b/serial/usb_serial.c:394
- sys/bus/u4b/usb_request.c:642-644
- sys/bus/u4b/usb_request.c:699-704
- sys/bus/u4b/usb_process.c:385-393
Detail
Exploit chain
evil MCS7703 (0x9710:0x7703) inserted; it never answers EP0 vendor data/status stages; every open(2) of /dev/ttyUx occupies the adapter's ucom taskqueue ~14.2 s uninterruptibly; close(2) and TIOCMSET/tcsetattr pile up behind it (each tcsetattr +4.1 s, user-amplifiable); pulling the device mid-cycle stalls the USB hub event thread up to the same ~14-18 s per cycle.
Evidence (decisive lines)
run.log ELAPSED lines: MO5456[A]=13922 ms, MO5456[2]=17923 ms, MO5456[B]=4109 ms (fix abort), MO5456[C]=1229 ms (fix abort+250ms), plus full per-task request-accounting traces; build.log (-Werror-clean in-tree kmod build); fix_compile.log RC=0 (11-hunk patched umoscom.ko).
PoC changes
Seed concept executed as the DF-5425 family rig: umoscom's exact production counts (4/5/3/2/0 requests per param/open/line-state/status/close task), production first-open queueing order (usb_serial.c:842 param, :865 open, :874-878 line, :880 status), production 1000 ms timeout, real usb_proc_create/msignal/mwait machinery, and the closer mirroring ucom_close's synchronous usb_proc_mwait.
Verified recommended fix
Bound the cfg-path control timeout (UMOSCOM_CFG_TIMEOUT 250 ms), make umoscom_cfg_write() return the error and abort the cfg_open/cfg_param chains on first failure (full diff in fix.diff).
Verdict
REPRODUCED on the guest (KLD-harness-with-production-code class; no MCS7703 device model exists): every umoscom cfg-callback step is a synchronous EP0 vendor request via ucom_cfg_do_request (1000 ms timeout) whose usb_error_t is discarded at every call site (umoscom.c:524-557), so no chain aborts on device failure. The first-open chain queues 14 sequential 1000 ms requests + 2 x hz/10 pauses on the per-adapter ucom taskqueue: cfg_param 4 writes (umoscom.c:454,457,460,492 + pause usb_serial.c:1636), cfg_open 5 writes (:368,371,377,380,383 + pause :777), line-state 3 writes (:415,:428,:402), status 2 reads (:506,:507). With the real in-kernel usb_process machinery and the exact per-request cost of an unanswered EP0 data stage (lksleep WXFER 1000 ms, usb_request.c:642-644/699-704), the synchronous close(2) behind one first open blocked 13922 ms (A) and 17923 ms with one extra user tcsetattr behind it (A2, double-buffered param task usb_serial.c:1691-1693) -- uninterruptible per usb_serial.c:671-672+955-957 / usb_process.c:385-393, umoscom_cfg_close itself being a no-op (umoscom.c:386-390). Unplug mid-cycle pins the hub event thread identically via ucom_detach->usb_proc_drain (usb_serial.c:394). A device answering at just-under-timeout makes every request succeed with no error to propagate. Fix mechanisms validated at runtime: abort-on-first-error 4109 ms (B), abort+250 ms bound 1229 ms (C).
No comments yet.