ubsa cfg callbacks chain synchronous 1000 ms-timeout EP0 vendor requests with discarded errors: an evil Belkin/eTek/GoHubs/Peracom adapter stretches every tcsetattr to 5.1 s (7.1 s for B0) and the first-open line-state cycle to 3.0 s of uninterruptible ucom-taskqueue occupancy, blocking close(2) SIGKILL-immune behind it (measured 9.97 s with one queued B0 tcsetattr) 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 (the coalesced line-state task; the first-open param task bails on the core LL_READY gate - a family-accounting precision: earlier siblings overcounted first opens), every tcsetattr = 5 requests + pause (~5.1s), the B0 fall-through = 7 (~7.1s). Harness on the real usb_process machinery with exact production queue order and per-request cost (unique per-request wchan like production's xfer ident - a harness fidelity fix caught this batch): closer blocked 2877/7893/9974ms; abort-on-error 2035ms, abort+250ms 557ms; patched production ubsa.ko compiles 9/9 hunks clean. Zero control-IN reads (the uninit class is structurally absent) and correct actlen>=4 in the intr callback - clean otherwise.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5476 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| ub5476stall_harness.c | β | 14.4 KB | view raw | |
| Makefile | β | 103 B | β download | |
| build.sh | β | 144 B | view raw | |
| run.sh | β | 166 B | view raw | |
| README.md | β | 3.9 KB | β raw | |
| VERDICT.md | β | 7.0 KB | β raw | |
| build.log | β | 7.1 KB | view raw | |
| run.log | β | 8.4 KB | view raw | |
| fix.diff | β | 2.8 KB | view raw | |
| fix_compile.log | β | 7.0 KB | view raw | |
| ubsa5476stall.ko | β | 11.5 KB | β download | |
| env.txt | β | 840 B | view raw | |
| manifest.json | β | 3.3 KB | view raw | |
| verdict.json | β | 7.5 KB | view raw |
DF-5476 β ubsa(4) cfg callbacks chain synchronous 1000 ms-timeout EP0 vendor requests with discarded errors: an evil Belkin/eTek/GoHubs/Peracom adapter stretches every tcsetattr to 5.1 s (7.1 s for B0) and the first-open line-state cycle to 3.0 s of uninterruptible ucom-taskqueue occupancy, blocking close(2) SIGKILL-immune behind it (measured 9.97 s with one queued B0 tcsetattr) and stalling the USB hub event thread on unplug (user-amplifiable)
Production file: sys/bus/u4b/serial/ubsa.c. Evidence class:
KLD-harness-with-production-code (no Belkin F5U103/eTek/GoHubs/Peracom
USB device model on the QEMU guest; same rig family as DF-5425 uslcom /
DF-5424 uftdi / DF-5449 uvscom / DF-5456 umoscom).
What the harness is
ub5476stall_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 ubsa's exact production
request counts, the production first-open queueing order including the
LL_READY gate (see below), and the production 1000 ms per-request
timeout. Each "request" is one lksleep("WXFER", 1000ms) on a unique
per-request wait channel β the exact production cost of an EP0
data/status stage an evil device never answers (usb_request.c:643,
:700-704).
Pass-2 accounting precision (LL_READY gate)
The first-open cfg_param task is queued at usb_serial.c:842 BEFORE
ucom_cfg_open (:865) and ucom_cfg_param bails at :1626-1628
unless UCOM_FLAG_LL_READY is set by the open task β so the first-open
param task is a no-op for every ucom driver, and ubsa (which registers
no ucom_cfg_open, so the core's hz/10 pause at usb_serial.c:777 does
not run either) issues only 3 Γ 1000 ms on first open, all from the
one coalesced line-state task (DTR+RTS+BREAK-off). The heavy chains arm
on every later tcsetattr while the tty is open.
Production chain accounting
| cycle | requests | ubsa.c | worst case |
|---|---|---|---|
| first open(2) | line-state 3 Γ write | :374,:384,:394 | 3.0 s |
| every tcsetattr (rate) | param 5 Γ write + core pause | :449,:460,:478,:482,:491 + usb_serial.c:1636 | 5.1 s |
| every tcsetattr (B0 hangup) | param 7 Γ write + core pause | :433,:434,:435,:460,:478,:482,:491 | 7.1 s |
| TIOCMSET (DTR+RTS) | line-state 2 Γ write | :374,:384 | 2.0 s |
| TIOCSBRK/TIOCCBRK | line-state 1 Γ write | :394 | 1.0 s |
| open/status/close tasks | 0 (no callbacks / cached) | :216-228, :535-543 | β |
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:364-393): the closer waits out every queued
param/line task. Unplug mid-cycle stalls the USB hub event thread via
ucom_detach() β usb_proc_drain() (usb_serial.c:394). A device
answering at just-under-timeout makes every request succeed β same
occupancy, no error anywhere.
Build
sh build.sh # /usr/src/sys/bus/u4b/serial/ub5476 + make (in-tree kmod)
Run
sh run.sh # kldload ubsa5476stall.ko (all phases at MOD_LOAD); dmesg
Expected (decisive) output β final measured values
UB5476[A]: closer: unblocked ELAPSED=2877 ms first open + close UB5476[2]: closer: unblocked ELAPSED=7893 ms + 1 tcsetattr@9600 UB5476[0]: closer: unblocked ELAPSED=9974 ms + 1 tcsetattr@B0 (7 reqs) UB5476[B]: closer: unblocked ELAPSED=2035 ms fix #1: abort on first error UB5476[C]: closer: unblocked ELAPSED=557 ms fix #1+#2: abort + 250 ms
Fix
fix.diff β bound the cfg-path control timeout (1000 β 250 ms via
UBSA_CFG_TIMEOUT), make ubsa_cfg_request() return the usb_error_t,
abort the ubsa_cfg_param() chain on the first failed request. Runtime
mechanism validated by phases B/C (9974 β 2035 β 557 ms); patched
production ubsa.ko builds clean on the guest (fix_compile.log,
9/9 hunks, zero compiler warnings).
DF-5476 VERDICT β ubsa cfg-chain synchronous-EP0 stall
Status: REPRODUCED (KLD-harness-with-production-code class; no Belkin/eTek/GoHubs/Peracom USB 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 ubsa cfg step is a synchronous EP0 vendor write via
ucom_cfg_do_request(ubsa.c:359-360 β usb_serial.h:220-221 βusbd_do_request_proc, usb_request.c:759) with a 1000 ms timeout (ubsa.c:360). Theusb_error_tis discarded at the only site (ubsa.c:361-364, "(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_queue_command(ucom_cfg_close)βusb_proc_mwait, usb_serial.c:671-672 + 955-957; uninterruptiblecv_wait, usb_process.c:364-393). 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. ubsa registers no ucom_cfg_open
(ubsa.c:216-228), so the core's hz/10 open pause (usb_serial.c:777)
does not run either, and ubsa_cfg_get_status reads cached bytes
(ubsa.c:541-542; kept fresh by the intr callback, ubsa.c:628-629 β 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 (ubsa.c:374, :384, :394).
Every later tcsetattr (param task double-buffered, usb_serial.c:1691-1693, LL_READY now set):
- non-B0 rate: baud(ubsa.c:449) + parity(:460) + data(:478) + stop(:482)
- flow(:491) = 5 Γ 1000 ms + hz/10 core pause (usb_serial.c:1636) β 5.1 s per tcsetattr;
- B0 (
cfsetospeed(B0)hangup): flow(:433) + DTR(:434) + RTS(:435) - parity + data + stop + flow = 7 Γ 1000 ms + pause β 7.1 s β
the fall-through past the
case B0:switch is what makes the hangup variant the heaviest ubsa chain; - TIOCMSET (DTR+RTS) β 2 Γ 1000 ms; TIOCSBRK/TIOCCBRK β 1 Γ 1000 ms.
Amplification: an open-file holder (ttyU is 0600 root:wheel,
usb_serial.c:477-478 β the device's login/console session) can queue N
tcsetattrs; the closer (and the hub event thread on unplug) waits out
N Γ 5.1 s (or 7.1 s each with B0). Measured with ONE queued tcsetattr:
close blocked 7893 ms (9600) / 9974 ms* (B0) after entering
usb_proc_mwait.
Harness and why it is faithful
ub5476stall_harness.c replicates, on the real usb_process
machinery: the exact first-open queue order including the LL_READY
gate; the 3/5/7-request production chains; the core's hz/10 param
pause; the hz/10Γ0 open pause (absent β no callback); the synchronous
closer (usb_proc_msignal+usb_proc_mwait, entered ~150 ms into the
chain like a real close(2) after open); and per-request cost =
lksleep("WXFER", USB_MS_TO_TICKS(timeout)).
Harness v1 β v2 fidelity bug, found and fixed during verification
(documented for honesty): v1 slept each simulated request on the ident
&ub_lock. Absolute-microtime instrumentation showed some requests
returning after ~9 ms instead of 1000 ms β DragonFly lockmgr handoffs
(the kldload thread's msignal lock dance overlapping the line-state
task) wake sleepers on the lock address, corrupting the timing model.
Production sleeps on the unique xfer pointer (usb_request.c:643),
which nothing but a completing transfer wakes. v2 gives each request
its own wait channel β after which every request slept its full
timeout and all five phases matched the production arithmetic exactly
(poc_changes field records this).
Decisive measurements (run.log, final v2 run)
UB5476[A][3049ms]: closer: unblocked ELAPSED=2877 ms # 3 reqs UB5476[2][8054ms]: closer: unblocked ELAPSED=7893 ms # 3 + 5 reqs + pause UB5476[0][10144ms]:closer: unblocked ELAPSED=9974 ms # 3 + 7 reqs + pause (B0) UB5476[B][2206ms]: closer: unblocked ELAPSED=2035 ms # fix: abort-on-error UB5476[C][728ms]: closer: unblocked ELAPSED=557 ms # fix: abort + 250 ms
Absolute per-request START timestamps in run.log show every request sleeping its full ~980-990 ms (tick slop) β e.g. A3's seven B0 requests at 3070/4052/5042/6042/7032/8032/9022 ms.
Fix validation
fix.diff (git-apply clean against sys/bus/u4b/serial/ubsa.c;
sha256 93d6d19a2b6c255b43fb0bb1ff860724550aae9a17323c4b545001bb3965898b):
bounds the cfg-path timeout (1000 β 250 ms, UBSA_CFG_TIMEOUT), makes
ubsa_cfg_request() return the error, aborts ubsa_cfg_param() on the
first failed request.
- Mechanism validated at runtime: phase B (abort-on-first-error) cuts the amplified closer from 9974 ms β 2035 ms; phase C (abort + 250 ms bound) β 557 ms (run.log).
- Patched production
ubsa.kocompiled on the guest: 9/9 hunks applied, zero compiler warnings/errors (fix_compile.log; the only "Warning" line is make's "Object directory not changed" notice). A full patched-kernel boot is not required to observe a per-request timeout bound + early-abort behavior the harness exercises directly (family standard, cf. DF-5456).
Why not panic/uid0
This is a resource-exhaustion/stall class defect (CWE-400), not memory corruption: the primitive is uninterruptible taskqueue occupancy with seconds-scale duration per cycle. Ceiling: per-adapter denial of service (the tty's close/unplug paths, the hub event thread while detaching), user-amplifiable to arbitrary multiples of 5.1/7.1 s while a session holds the tty open. Family precedent (DF-5421/5424/5425/ 5449/5456): Low severity, base:hw bucket.
Guest hygiene
Harness module loaded/unloaded, dmesg ring used, /usr/src scratch dir
ub5476/ + /root/df5476 left behind; production ubsa.c restored
(verified byte-identical). vm.sh reset with-src performed after the
run β the guest is returned clean.
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 9974 ms to 2035 ms and phase C (abort + 250 ms bound) to 557 ms; the 9-hunk fix.diff (error-returning ubsa_cfg_request, chain aborts, bounded UBSA_CFG_TIMEOUT) applies 9/9 clean and compiles the patched production ubsa.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 93d6d19a2b6c255b43fb0bb1ff860724550aae9a17323c4b545001bb3965898b); fix_compile.log (patched production ubsa.ko, only make's 'Object directory not changed' notice, no compiler diagnostics); run.log phases B/C ELAPSED lines.
Confirmed kernel references
- sys/bus/u4b/serial/ubsa.c:346-365
- sys/bus/u4b/serial/ubsa.c:359-360
- sys/bus/u4b/serial/ubsa.c:361-364
- sys/bus/u4b/serial/ubsa.c:374
- sys/bus/u4b/serial/ubsa.c:384
- sys/bus/u4b/serial/ubsa.c:394
- sys/bus/u4b/serial/ubsa.c:424-492
- sys/bus/u4b/serial/ubsa.c:433-435
- sys/bus/u4b/serial/ubsa.c:449
- sys/bus/u4b/serial/ubsa.c:460
- sys/bus/u4b/serial/ubsa.c:478
- sys/bus/u4b/serial/ubsa.c:482
- sys/bus/u4b/serial/ubsa.c:491
- sys/bus/u4b/serial/ubsa.c:535-543
- sys/bus/u4b/serial/usb_serial.c:671-672
- sys/bus/u4b/serial/usb_serial.c:771-778
- 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/usb_request.c:643
- sys/bus/u4b/usb_request.c:700-704
- sys/bus/u4b/usb_process.c:364-393
Detail
Exploit chain
evil adapter (Belkin 050d:0001/0002, eTek 057e:0002, GoHubs 1002:0002/08ff, Peracom 0565:0001, AnyData, Axesstel) 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; every tcsetattr adds 5.1 s (7.1 s with B0) while a session holds the tty open (user-amplifiable to arbitrary multiples); close(2) blocks SIGKILL-immune behind the queue (measured 9.97 s with one queued B0 tcsetattr); 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: UB5476[A]=2877 ms, [2]=7893 ms, [0]=9974 ms (B0), [B]=2035 ms (fix abort), [C]=557 ms (fix abort+250ms), plus absolute per-request START timestamps showing every request sleeping its full ~990 ms (A3's seven B0 requests at 3070/4052/5042/6042/7032/8032/9022 ms); build.log (-Werror-clean in-tree kmod build); fix_compile.log (9/9 hunks, patched production ubsa.ko, zero compiler warnings).
PoC changes
Seed concept executed as the DF-5425/5456 family rig adapted to ubsa's exact production shapes: 3/5/7-request chains (line-state / tcsetattr / tcsetattr(B0) with its fall-through-past-the-switch accounting), 0-request open/status/close tasks, the production LL_READY gate that makes the first-open param task bail (usb_serial.c:1626-1628 -- a pass-2 precision the earlier sibling accounting missed), absent core open-pause (no callback), production 1000 ms timeout, real usb_proc machinery, closer mirroring ucom_close's synchronous usb_proc_mwait entered ~150 ms into the chain. Harness v1 fidelity bug caught by absolute-timestamp instrumentation and fixed: simulated requests must sleep on a UNIQUE per-request wait channel (production sleeps on the unique xfer pointer, usb_request.c:643), not on the lock address, or unrelated lockmgr handoffs wake them early (~9 ms instead of 1000 ms).
Verified recommended fix
Bound the cfg-path control timeout (UBSA_CFG_TIMEOUT 250 ms) and abort the ubsa_cfg_param() chain on the first failed request via an error-returning ubsa_cfg_request() (full diff in fix.diff).
Verdict
REPRODUCED on the guest (KLD-harness-with-production-code class; no Belkin F5U103/eTek/GoHubs/Peracom device model exists): every ubsa cfg-callback step is a synchronous EP0 vendor write via ucom_cfg_do_request with a 1000 ms timeout (ubsa.c:359-360) whose usb_error_t is discarded at the only site (ubsa.c:361-364, '(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 ubsa 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 (ubsa.c:374,:384,:394); measured closer block 2877 ms. Every later tcsetattr re-queues the param task (double-buffered, usb_serial.c:1691-1693): 5 x 1000 ms + hz/10 core pause (:1636) = ~5.1 s, or 7 x 1000 ms + pause = ~7.1 s for the B0 hangup variant (case B0 falls through to parity/data/stop/flow, ubsa.c:432-492); measured closer block 7893 ms (one 9600 tcsetattr) and 9974 ms (one B0 tcsetattr) behind first open -- uninterruptible per usb_serial.c:671-672+955-957 / usb_process.c:364-393, ubsa_cfg_close being a 0-request no-op (no callback registered, ubsa.c:216-228). 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 (production sleeps on the unique xfer pointer). Fix mechanisms validated at runtime: abort-on-first-error 2035 ms (B), abort+250 ms bound 557 ms (C); patched production ubsa.ko compiles 9/9 hunks clean.
No comments yet.