umct cfg callbacks chain sequential synchronous 1000 ms-timeout EP0 vendor requests with discarded errors: an evil MCT/Belkin/Sitecom adapter stretches the first-open cycle to ~4.0 s and every tcsetattr (any rate, incl. B0 β no branch) to ~2.1 s of uninterruptible ucom-taskqueue occupancy, blocking close(2) SIGKILL-immune behind the queue (measured 5.95 s with one queued tcsetattr, 8.05 s with a 5-burst; a sustained loop extends without bound) and stalling the USB hub event thread on unplug (user-amplifiable)
Summary
Every cfg step is a sync 1000ms EP0 write with the error discarded: first open = 3 requests, every tcsetattr = 2 + pause (no B0 branch - umct_pre_param accepts everything), close +1 - and cv_wait drops sc_lock so a sustained unpriv tcsetattr loop extends the SIGKILL-immune close block without bound (burst ceiling 2 queued per the msignal double buffer). Harness: closer blocked 3885/5952/8049ms; abort-on-error 3033ms, abort+250ms 834ms; 8/8-hunk fix compiles the patched production umct.ko. Zero control-IN reads (uninit class structurally absent) and correct actlen gate - memory-clean otherwise.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5496 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.6 KB | β raw | |
| VERDICT.md | β | 8.2 KB | β raw | |
| um5496stall_harness.c | β | 16.9 KB | view raw | |
| Makefile | β | 103 B | β download | |
| build.sh | β | 144 B | view raw | |
| run.sh | β | 203 B | view raw | |
| build.log | β | 7.1 KB | view raw | |
| run.log | β | 9.8 KB | view raw | |
| fix_compile.log | β | 7.3 KB | view raw | |
| env.txt | β | 254 B | view raw | |
| fix.diff | β | 2.5 KB | view raw | |
| manifest.json | β | 3.2 KB | view raw | |
| verdict.json | β | 9.0 KB | view raw |
DF-5496 β umct(4) cfg-chain synchronous-EP0 stall (ucom family)
File: sys/bus/u4b/serial/umct.c (Magic Control Technology USB-RS232,
Belkin F5U109/F5U409, Sitecom; ucom core)
Class: cfg-stall family (cf. DF-5476 ubsa / DF-5421 uchcom / DF-5424
uftdi / DF-5425 uslcom / DF-5449 uvscom / DF-5456 umoscom / DF-5487 uvisor)
Evidence class: KLD-harness-with-production-code (no MCT USB device
model exists on the QEMU guest; the harness drives the real in-kernel
usb_process taskqueue machinery that ucom's cfg callbacks, synchronous
close and detach drain run on, with umct's exact production request
counts and the production 1000 ms timeout).
Build (on the guest)
sh build.sh # cd /usr/src/sys/bus/u4b/serial/um5496 && make
# artifact: umct5496stall.ko (-Werror clean)
Run (on the guest)
sh run.sh # dmesg -c; kldload /root/df5496/umct5496stall.ko (~23 s,
# all phases at MOD_LOAD); kldunload; dmesg | grep UM5496
Expected decisive output (measured 2026-10-06)
UM5496[A]: closer: unblocked ELAPSED=3885 ms (first open: 3-req
line-state task + 1-req HUPCL DTR drop; first-open param
bails !LL_READY)
UM5496[2]: closer: unblocked ELAPSED=5952 ms (+ 1 tcsetattr: 2 reqs
+ hz/10 pause)
UM5496[3]: closer: unblocked ELAPSED=8049 ms (+ burst of 5 tcsetattr
coalesced to 2 param executions β production msignal
double-buffer ceiling; a paced/sustained loop extends
without bound until the abuser stops)
UM5496[B]: closer: unblocked ELAPSED=3033 ms (fix#1: abort on error)
UM5496[C]: closer: unblocked ELAPSED=834 ms (fix#1+#2: abort+250 ms)
Every simulated request sleeps its full ~980β1000 ms (absolute timestamps in run.log), i.e. each is the real in-kernel cost of an unanswered EP0 data/status stage.
Fix validation
fix.diff (8 hunks): error-returning umct_cfg_do_request(), chain
aborts in umct_cfg_set_{break,dtr,rts}/umct_cfg_param, cfg-path
timeout bounded 1000 β 250 ms (UMCT_CFG_TIMEOUT). Applies 8/8 clean
(git apply --check + guest patch -p1) and the patched production
umct.ko compiles with zero compiler diagnostics (fix_compile.log;
the only "error" string in the log is the literal -Werror flag).
Artifacts
um5496stall_harness.cβ the rig (self-contained KLD)Makefile,build.sh,run.shβ exact build/runbuild.log/run.log/fix_compile.logβ full untrimmed logsenv.txtβ guest uname/compilerfix.diffβ git-apply-able unified diff (never applied tosys/)VERDICT.mdβ full narrative;manifest.json+verdict.json
DF-5496 VERDICT β umct cfg-chain synchronous-EP0 stall
Status: REPRODUCED (KLD-harness-with-production-code class; no MCT/Belkin/Sitecom USB serial device model exists on the QEMU guest). Impact: dos (local, evil-USB precondition; uninterruptible occupancy of the per-adapter ucom taskqueue; blocks close(2) SIGKILL-immune and the USB hub event thread on unplug). Confidence: certain (every simulated request is the real in-kernel cost of an unanswered EP0 data/status stage, on the real usb_process taskqueue, with production request counts and queueing order).
Root cause (path:line)
- Every umct cfg step is a synchronous EP0 vendor write via
ucom_cfg_do_request(umct.c:332-333 β usb_serial.h:220-221 βusbd_do_request_proc) with a 1000 ms timeout (umct.c:333). Theusb_error_tis discarded at the only site there is (umct.c:334-337, "(ignored)"), so no chain ever aborts on failure. - An evil adapter that never answers the data/status stage makes each
request eat its full timeout (usb_request.c:561
max_ticks, :643 "WXFER" sleep, :700-704USB_ERR_TIMEOUT); an adapter answering at just-under-timeout makes every request succeed β identical occupancy, no error to propagate. - Chains run on the per-adapter ucom taskqueue
(
sc_super_ucom.sc_tq, usb_serial.c:322). close(2) is synchronous against it (ucom_closeβucom_shutdown(HUPCL, one more request) βucom_queue_command(ucom_cfg_close)βusb_proc_mwait, usb_serial.c:671-672 + 953-957; uninterruptiblecv_wait, usb_process.c:364-393 β a plain cv_wait, no signal delivery, that drops sc_lock while asleep, so a concurrently-queueing tcsetattr loop extends the wait until it stops). Unplug stalls the USB hub event thread the same way (ucom_detachβusb_proc_drain, usb_serial.c:394).
Production chain accounting (pass-2 precision)
The core queues, on first open (usb_serial.c:842 param, :870 start,
:865 cfg_open, :870 start, :874-878 line-state, :880 status):
the first param task runs before the cfg_open task and bails at
usb_serial.c:1626-1628 (!UCOM_FLAG_LL_READY) β the first-open param
is a no-op for every ucom driver. umct registers no ucom_cfg_open
(umct.c:175-187), so the core's hz/10 open pause (usb_serial.c:777)
does not run either, and umct_cfg_get_status reads cached bytes
(umct.c:386-387; kept fresh by umct_intr_callback_sub, umct.c:360-361
β no EP0). What issues EP0 on first open is the single coalesced
line-state task (usb_serial.c:1357-1420): DTR=1 + RTS=1 + BREAK=0 β
3 Γ 1000 ms (umct.c:413 set_dtr SET_MCR, :426 set_rts SET_MCR,
:400 set_break(0) SET_LCR). Close adds the HUPCL DTR drop
(usb_serial.c:694-696 β umct.c:413): +1 Γ 1000 ms.
Every later tcsetattr (param task double-buffered, usb_serial.c:1691-1693, LL_READY now set):
- any rate β there is no B0 branch in umct_cfg_param (umct.c:465-504); umct_pre_param accepts everything (umct.c:459-463) and the same two registers are programmed for B0 as for any speed: SET_BAUD (umct.c:472) + SET_LCR (umct.c:503) = 2 Γ 1000 ms + hz/10 core pause (usb_serial.c:1636) β 2.1 s per tcsetattr;
- TIOCMSET (DTR+RTS) β 2 Γ 1000 ms; TIOCSBRK/TIOCCBRK β 1 Γ 1000 ms.
Amplification: the msignal double buffer COALESCES a rapid tcsetattr
burst to at most 2 queued instances (usb_process.c:259-331, t==3
branch) β a burst of 5 measured 2 full param executions (phase A3) β
but a paced or sustained tcsetattr loop keeps the taskqueue busy
~2.1 s per instance for as long as the abuser keeps queueing, and
because usb_proc_mwait's cv_wait drops sc_lock while asleep, the
closer cannot lock them out: close(2) blocks SIGKILL-immune until the
abuser stops plus up to 2 Γ 2.1 s residual + 1.0 s HUPCL. Measured
with the closer entering ~150 ms into the chain: first open + close
3885 ms; +1 tcsetattr 5952 ms; +5-burst tcsetattr 8049 ms.
Unplug mid-cycle pins the USB hub event thread identically
(usb_serial.c:394) per cycle.
Harness and why it is faithful
um5496stall_harness.c replicates, on the real usb_process
machinery: the exact first-open queue order including the LL_READY
gate; the 3/2/1-request production chains (line-state / tcsetattr /
HUPCL-close); the core's hz/10 param pause; the absent core open-pause
(no callback); the synchronous closer (HUPCL line task queued via
msignal, then usb_proc_msignal+usb_proc_mwait, entered ~150 ms into
the chain like a real close(2) after open); per-request cost =
lksleep("WXFER", USB_MS_TO_TICKS(timeout)) on a unique per-request
wait channel (production sleeps on the unique xfer pointer,
usb_request.c:643 β the DF-5476 harness v1 lesson carried here from
day one); and the production double-buffer coalescing semantics
(phase A3 burst of 5 β 2 param executions, instrumented).
Fix validation
fix.diff(8 hunks):umct_cfg_do_request()returnsusb_error_t;umct_cfg_set_break/set_dtr/set_rtsabort on failure;umct_cfg_paramaborts the chain on the first failed SET_BAUD; cfg-path timeout boundedUMCT_CFG_TIMEOUT= 250 ms (umct.c:333 was 1000).- Mechanism-level validation per family standard: harness phase B
(abort-on-first-error) cuts the measured closer block from 5952 ms
to 3033 ms and phase C (abort + 250 ms bound) to 834 ms
(run.log); the 8-hunk fix.diff applies 8/8 clean (
git apply --checkon the audit tree;patch -p1on the guest) and compiles the patched production umct.ko with zero compiler diagnostics on the guest (fix_compile.log,-Werror; the only "error" string in the log is the literal-Werrorflag). A full patched-kernel boot is not required to observe a per-request timeout bound + early-abort behavior the harness exercises directly.
Honesty notes
- First fix.diff draft used
!= USB_ERR_OK, which does not exist in this tree's usb.h (success is implicitly 0) β the patched module failed to compile; corrected to!= 0(family precedent, DF-5476) and rebuilt clean. Attempt count includes this. - No MCT hardware exists on the guest; the device's side of each request (never answering the data/status stage) is simulated by the documented in-kernel cost of exactly that condition (the WXFER timeout sleep), not by a USB device model.
Negative results this pass (why umct is otherwise clean)
- No uninit-return class: umct issues ZERO control-IN reads
anywhere β
UMCT_GET_MSR/UMCT_GET_LCR(umct.c:72-75) are dead constants; status is served from intr-cachedsc_lsr/sc_msr(umct.c:381-388). The DF-1062/2342/5448/5455 class cannot exist. - Correct intr length gate:
umct_intr_callback_subchecksactlen < 2before copying the 2-byte status payload (umct.c:353-358) β the ulpt DF-5438 class is absent. Read path passesactlen(device-controlled but bounded by the frame length) toucom_put_data(umct.c:605-610); write path bounded by the core-computedsc_obufsize(umct.c:281, SITECOM clamp :283-287). - No sync EP0 from the USB callback thread (umcs DF-5382 class absent): all three xfer callbacks only copy data and resubmit.
- Attach/detach error paths safe: both
umct_attachfailure branches (umct.c:258-262 transfer-setup, :290-292 ucom_attach) gotoumct_detach;ucom_detachno-ops unlessUCOM_FLAG_ATTACHED(usb_serial.c:371-372) anducom_attachself-cleans partial failure (usb_serial.c:344-348);usbd_transfer_unsetupon NULL xfers is a no-op. No uether DF-5334 goto-detach panic class. - Endpoint-swap heuristic memory-safe (umct.c:269-279): swaps
whole xfer pointers whose DMA buffers stay sized to their own
endpoints; dispatch guarded by
sc_swap_cb(umct.c:547-566); written once at attach beforeucom_attach(no race window). - umct_calc_baud/pre_param: unsupported rates (B50..B200, B230400+, arbitrary speeds) silently program divisor 0 β a functional wrong-baud issue (Linux mct_u232 maps divisor 0 to 2400 baud), upstream-inherited, no security impact (not filed).
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 5952 ms to 3033 ms and phase C (abort + 250 ms bound) to 834 ms; the 8-hunk fix.diff (error-returning umct_cfg_do_request, chain aborts in set_break/set_dtr/set_rts/cfg_param, bounded UMCT_CFG_TIMEOUT) applies 8/8 clean (git apply --check on the audit tree, patch -p1 on the guest) and compiles the patched production umct.ko with zero compiler warnings on the guest (fix_compile.log). A full patched-kernel boot is not required to observe a per-request timeout bound + early-abort behavior the harness exercises directly.
fix.diff (sha256 b54552212a69cf7decff27f5f8506a0407726ae5bd166a3d9b110e2a00b92f11); fix_compile.log (patched production umct.ko, zero compiler diagnostics; the only 'error' string is the literal -Werror flag); run.log phases B/C ELAPSED lines (3033 ms / 834 ms vs baseline 5952 ms).
Confirmed kernel references
- sys/bus/u4b/serial/umct.c:314-339
- sys/bus/u4b/serial/umct.c:332-333
- sys/bus/u4b/serial/umct.c:334-337
- sys/bus/u4b/serial/umct.c:390-401
- sys/bus/u4b/serial/umct.c:400
- sys/bus/u4b/serial/umct.c:404-414
- sys/bus/u4b/serial/umct.c:413
- sys/bus/u4b/serial/umct.c:416-427
- sys/bus/u4b/serial/umct.c:426
- sys/bus/u4b/serial/umct.c:459-463
- sys/bus/u4b/serial/umct.c:465-504
- sys/bus/u4b/serial/umct.c:472
- sys/bus/u4b/serial/umct.c:503
- sys/bus/u4b/serial/usb_serial.c:322
- sys/bus/u4b/serial/usb_serial.c:671-672
- sys/bus/u4b/serial/usb_serial.c:694-696
- sys/bus/u4b/serial/usb_serial.c:771-778
- 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
- sys/bus/u4b/serial/usb_serial.c:874-880
- sys/bus/u4b/serial/usb_serial.c:939-957
- sys/bus/u4b/serial/usb_serial.c:394
- sys/bus/u4b/serial/usb_serial.c:1357-1420
- sys/bus/u4b/serial/usb_serial.c:1620-1637
- sys/bus/u4b/serial/usb_serial.c:1691-1693
- sys/bus/u4b/serial/usb_serial.h:220-221
- sys/bus/u4b/usb_request.c:561
- sys/bus/u4b/usb_request.c:643
- sys/bus/u4b/usb_request.c:700-704
- sys/bus/u4b/usb_process.c:259-331
- sys/bus/u4b/usb_process.c:364-393
Detail
Exploit chain
evil adapter (MCT USB232 0711:0210, Sitecom USB232 6189:2068, D-Link DU-H3SP, Belkin F5U109/F5U409 050d:010a/0129) inserted; it never answers EP0 vendor data/status stages (or answers at just-under-timeout, making every request succeed with no error anywhere); first open(2) of /dev/ttyUx occupies the adapter's ucom taskqueue ~3.0 s uninterruptibly (+1.0 s HUPCL at close); every tcsetattr adds ~2.1 s while a session holds the tty open (user-amplifiable: sustained loop keeps the tq busy 2.1 s per instance indefinitely; burst ceiling 2 queued instances); close(2) blocks SIGKILL-immune behind the queue (measured 5.95 s / 8.05 s); pulling the device mid-cycle stalls the USB hub event thread up to the same duration per cycle.
Evidence (decisive lines)
run.log ELAPSED lines: UM5496[A]=3885 ms, [2]=5952 ms, [3]=8049 ms (5-burst), [B]=3033 ms (fix abort), [C]=834 ms (fix abort+250ms), plus absolute per-request START timestamps showing every request sleeping its full ~980 ms (e.g. phase A: 57/1038/2028/3049/4058 ms) and the phase-A3 coalescing instrumentation (burst of 5 -> 2 param-task executions); build.log (-Werror-clean in-tree kmod build); fix_compile.log (8/8 hunks, patched production umct.ko, zero compiler diagnostics -- only the literal -Werror flag matches 'error').
PoC changes
Seed concept executed as the DF-5476 ubsa family rig adapted to umct's exact production shapes: 3-request first-open line-state chain, 2-request tcsetattr chain (identical for B0 -- umct has no B0 branch, unlike ubsa's 7-request B0 variant), 1-request HUPCL close drop modeled as a dedicated tq task queued by the closer (production ucom_shutdown ordering), 0-request open/status/close tasks, the production LL_READY gate that makes the first-open param task bail (usb_serial.c:1626-1628), absent core open-pause (no callback), production 1000 ms timeout (umct.c:333), real usb_proc machinery, closer mirroring ucom_close's synchronous usb_proc_mwait entered ~150 ms into the chain, and the DF-5476 v2 fidelity lesson applied from the start (unique per-request wait channels). New this instance: an amplification phase (5-burst) instrumenting the production msignal double-buffer coalescing ceiling (2 executions), and the first fix.diff draft used != USB_ERR_OK which does not exist in this tree's usb.h -- caught by the on-guest compile (build_failed -> corrected to != 0 per family precedent, rebuilt clean).
Verified recommended fix
Bound the cfg-path control timeout (UMCT_CFG_TIMEOUT 250 ms) and abort the umct_cfg_param()/set_* chains on the first failed request via an error-returning umct_cfg_do_request() (full diff in fix.diff).
Verdict
REPRODUCED on the guest (KLD-harness-with-production-code class; no MCT USB232/Sitecom/Belkin F5U109/F5U409 device model exists): every umct cfg-callback step is a synchronous EP0 vendor write via ucom_cfg_do_request with a 1000 ms timeout (umct.c:332-333) whose usb_error_t is discarded at the only site (umct.c:334-337, '(ignored)'), so no chain aborts on device failure. Pass-2 accounting precision: the first-open cfg_param task is queued before ucom_cfg_open (usb_serial.c:842 vs :865) and bails at :1626-1628 (!LL_READY) -- first-open param is a no-op for every ucom driver -- and umct registers no ucom_cfg_open (:777 pause skipped), so first open issues only the coalesced line-state chain DTR+RTS+BREAK-off = 3 x 1000 ms (umct.c:413,:426,:400), plus the HUPCL DTR drop at close (usb_serial.c:694-696 -> umct.c:413, 1 x 1000 ms); measured closer block 3885 ms. Every later tcsetattr re-queues the param task (double-buffered, usb_serial.c:1691-1693): SET_BAUD(umct.c:472) + SET_LCR(umct.c:503) + hz/10 core pause (:1636) = ~2.1 s -- IDENTICAL for B0 and any rate (no B0 branch in umct_cfg_param; umct_pre_param accepts everything, umct.c:459-463); measured closer block 5952 ms (one tcsetattr) and 8049 ms (burst of 5, coalesced to 2 full param executions by the msignal double buffer, usb_process.c:259-331 -- a paced/sustained loop extends the block without bound because usb_proc_mwait's uninterruptible cv_wait drops sc_lock while asleep, usb_process.c:364-393) -- uninterruptible per usb_serial.c:671-672+953-957; umct registers no ucom_cfg_close so the cfg_close task itself is a 0-request no-op. Unplug mid-cycle pins the USB hub event thread identically via ucom_detach->usb_proc_drain (usb_serial.c:394). Each simulated request is the real in-kernel cost of an unanswered EP0 data/status stage (lksleep WXFER 1000 ms, usb_request.c:643/700-704) on the real usb_process taskqueue, per-request unique wait channel. Fix mechanisms validated at runtime: abort-on-first-error 3033 ms (B), abort+250 ms bound 834 ms (C); patched production umct.ko compiles 8/8 hunks clean.
No comments yet.