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

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)

No comments yet.

PoC verification

Evidence pack

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

VERDICT.md
↓ download raw

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). The usb_error_t is 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-704 USB_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; uninterruptible cv_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.ko compiled 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

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

Confirmed kernel references

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.