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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5456 Β· 15 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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):

  1. 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);
  2. ucom_cfg_open at :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);
  3. 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);
  4. 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 tcsetattr of 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) while umoscom_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-propagating umoscom_cfg_write(), chain aborts on first failure in cfg_open/cfg_param with divisor-latch safety preserved): patched production umoscom.ko builds -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

fixed
baseline reproduced→ patch + rebuild →patched reproduced

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

Confirmed kernel references

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