uark cfg-chain synchronous-EP0 stall: uark_cfg_param chains six 1000 ms uninterruptible control writes whose errors are discarded β a silent/stalling ARK3116 (or unprivileged tcsetattr loop against one) parks the shared ucom taskqueue ~6.1 s per tcsetattr, blocking SIGKILL-immune close(2) behind it and stalling the USB hub event thread on unplug/detach drain (cfg-stall family, DF-5532 occupancy delta)
Summary
Every cfg step is a synchronous 1000ms EP0 vendor write whose usb_error_t is discarded at the only site, so no chain aborts: uark_cfg_param chains SIX writes per tcsetattr (~6.1s occupancy - 3x umct and the heaviest single-rate chain short of umcs' 28-request open), first open 1s, break toggles 1s each. close(2) waits uninterruptibly behind the queue (measured 7.0s after one tcsetattr, 13.0s after a 5-burst - coalesced to 2 executions); unplug stalls the hub event thread via usb_proc_drain. Production-machinery harness (real usb_process taskqueue, exact request counts/timeouts): 921/6995/13012ms phases; abort-on-error fix -> 2029ms, abort+250ms -> 561ms; fix.diff applies 6/6, patched uark.ko compiles -Werror clean. Divisor math (3000000+speed/2)/speed over [300,115200] fits uint16; zero control-INs anywhere so the uninit-return class is structurally absent.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5540 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| uark5540stall_harness.c | β | 17.6 KB | view raw | |
| Makefile | β | 105 B | β download | |
| build.sh | β | 159 B | view raw | |
| run.sh | β | 190 B | view raw | |
| build.log | β | 7.1 KB | view raw | |
| run.log | β | 9.8 KB | view raw | |
| run.2.log | β | 269 B | view raw | |
| fix_compile.log | β | 7.0 KB | view raw | |
| env.txt | β | 262 B | view raw | |
| fix.diff | β | 2.0 KB | view raw | |
| README.md | β | 3.0 KB | β raw | |
| VERDICT.md | β | 8.3 KB | β raw | |
| manifest.json | β | 2.7 KB | view raw | |
| verdict.json | β | 9.9 KB | view raw |
DF-5540 β uark(4) cfg-chain synchronous-EP0 stall (ucom family)
File: sys/bus/u4b/serial/uark.c (Arkmicro ARK3116 USB-RS232; ucom core)
Class: cfg-stall family (cf. DF-5367 ufoma / DF-5383 umcs / DF-5407 uplcom
/ DF-5421 uchcom / DF-5424 uftdi / DF-5425 uslcom / DF-5449 uvscom / DF-5456
umoscom / DF-5476 ubsa / DF-5487 uvisor / DF-5496 umct / DF-5504 ucycom;
root occupancy-gating mechanism DF-5532)
Evidence class: KLD-harness-with-production-code (no Arkmicro ARK3116 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 uark'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/ua5540 && make
# artifact: uark5540stall.ko (-Werror clean)
Run (on the guest)
sh run.sh # dmesg -c; kldload /root/df5540/uark5540stall.ko (~25 s,
# all phases at MOD_LOAD); kldunload; dmesg | grep UA5540
Expected decisive output (measured 2026-10-07)
UA5540[A]: closer: unblocked ELAPSED=921 ms (first open: 1-request
break-off line-state task -- ONLY set_break is registered
among uark's line callbacks; param bails !LL_READY)
UA5540[2]: closer: unblocked ELAPSED=6995 ms (+ 1 tcsetattr: 6 reqs
+ hz/10 pause = 6.1 s -- heaviest single-rate chain in the
audited family short of umcs' 28-request open/close)
UA5540[3]: closer: unblocked ELAPSED=13012 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)
UA5540[B]: closer: unblocked ELAPSED=2029 ms (fix#1: abort on error)
UA5540[C]: closer: unblocked ELAPSED=561 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. Stability re-run (run.2.log): 854 / 6950 / 12963 / 2034 / 564 ms.
Fix validation
fix.diff (6 hunks): error-returning uark_cfg_write(), chain aborts in
uark_cfg_param() (all 6 steps checked), bounded cfg-path timeout
1000 β 250 ms (UARK_CFG_TIMEOUT). Applies 6/6 clean (git apply
--check on the audit tree + guest patch -p1) and the patched
production uark.ko compiles with zero compiler diagnostics
(fix_compile.log; the only "error" string in build logs is the literal
-Werror flag).
Artifacts
uark5540stall_harness.cβ the rig (self-contained KLD)Makefile,build.sh,run.shβ exact build/runbuild.log/run.log/run.2.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-5540 VERDICT β uark(4) cfg-chain synchronous-EP0 stall
Status: REPRODUCED (impact: dos; confidence: certain)
Evidence class: KLD-harness-with-production-code on the QEMU guest
(DragonFly 6.5-DEVELOPMENT, env.txt), family standard β no Arkmicro
ARK3116 (6547:0232) device model exists in the rig.
Root cause (production code, line-accurate)
Every uark cfg callback step is a synchronous EP0 vendor control write
(UARK_WRITE 0x40 / UARK_REQUEST 0xfe / wLength 0) issued through
ucom_cfg_do_request() (uark.c:429-430 β usb_serial.h:220-221 β
usbd_do_request_proc, usb_request.c:759-790) with a 1000 ms timeout,
and the usb_error_t result is discarded at the only site there is
(uark.c:431-434, "(ignored)"). No chain step ever aborts on device
failure. A silent evil adapter makes each request eat its full 1000 ms
timeout (usb_request.c:561 max_ticks, :643 "WXFER" sleep, :700-704
USB_ERR_TIMEOUT); a slow-but-answer-at-999 ms adapter makes every request
succeed. All of this executes on the per-adapter ucom usb_process
taskqueue (sc_tq, usb_serial.c:322) β the same queue whose synchronous
usb_proc_mwait() close (usb_serial.c:671-672) and usb_proc_drain()
detach (usb_serial.c:394) wait uninterruptibly (usb_process.c:364-393),
and whose empty-queue-gated wakeup is the DF-5532 root defect.
Pass-2 production chain accounting (all verified against source)
- First open(2) of
/dev/ttyUx(ucom_open, usb_serial.c:801-903): param queued at :842 runs before cfg_open (:865) and bails (!LL_READY, :1626-1628) β 0 requests. uark registers noucom_cfg_open(uark.c:138-148) β cfg_open sets LL_READY with no hz/10 pause (:771-778). The coalesced first-open line-state task (:874-878 β :1357-1420) computes its callback mask from what is registered (:1374-1381): uark registers onlyucom_cfg_set_breakamong the line callbacks (uark.c:140; no set_dtr/set_rts/set_ring), so DTR+RTS+BREAK-off collapses to 1 Γ 1000 ms (uark_cfg_set_break(sc,0)βuark_cfg_write(sc,4,0x00), uark.c:414). Status task: cached bytes only β 0 requests. β first open β 1.0 s taskqueue occupancy. - Every later tcsetattr (param task, LL_READY set; double-buffered
usb_serial.c:1691-1693):
uark_cfg_param(uark.c:348-396) chains six synchronous writes β uark.c:361 (reg3=0x83), :362 (reg0=div LSB), :363 (reg1=div MSB), :364 (reg3=0x03), :394 (reg3=0x00), :395 (reg3=LCR) β plus the core hz/10 pause (:1636) β 6.1 s per tcsetattr at ANY accepted rate (300..115200, uark.c:343; B0/0 and115200 are rejected in pre_param β there is no B0 variant). This is 3Γ umct's per-tcsetattr cost (DF-5496) and the heaviest single-rate chain in the audited family short of umcs' 28-request open/close pair (DF-5383).
- TIOCSBRK/TIOCCBRK β line-state task β
uark_cfg_set_break(uark.c:407-415) β 1 Γ 1000 ms per toggle. - close(2): synchronous β
ucom_close()βucom_shutdown()HUPCL DTR drop (usb_serial.c:694-696) is masked out (set_dtr NULL, :1374-1375) β 0 requests of the closer's own; then cfg_close (no callback β 0 requests) +usb_proc_mwait()uninterruptibly behind whatever is queued. Unplug mid-cycle stalls the USB hub event thread identically via ucom_detach β usb_proc_drain (usb_serial.c:394).
What the harness measured (real usb_proc machinery, real timeouts)
Closer thread enters ~100β150 ms into the chain and blocks in the
production usb_proc_mwait():
| Phase | Configuration | Measured (run.log) | Re-run (run.2.log) |
|---|---|---|---|
| A | first open + close | 921 ms | 854 ms |
| A2 | open + 1 tcsetattr + close | 6995 ms | 6950 ms |
| A3 | open + 5-tcsetattr burst + close | 13012 ms | 12963 ms |
| B | fix#1 abort-on-error | 2029 ms | 2034 ms |
| C | fix#1+#2 abort + 250 ms | 561 ms | 564 ms |
Every simulated request slept its full ~980β1000 ms (absolute START
timestamps in run.log, e.g. phase A2: 1088/2070/3061/4061/5061/6050 ms).
The A3 instrumentation shows the production usb_proc_msignal t==3
double-buffer coalescing ceiling (burst of 5 β 2 param executions,
usb_process.c:259-331); a paced or sustained tcsetattr loop keeps the
taskqueue busy ~6.1 s per instance for as long as the abuser keeps
queueing, and usb_proc_mwait's uninterruptible cv_wait drops sc_lock
while asleep β the block extends without bound until the abuser stops.
Threat model / impact ceiling
- Evil ARK3116-class adapter (or any uark device whose EP0 goes silent mid-operation): pins the per-adapter ucom cfg taskqueue 6.1 s per tcsetattr, ~1.0 s per open; the same user's close(2) is SIGKILL-immune-blocked behind it; unplug stalls the shared USB hub event thread up to the same duration per cycle (usb_serial.c:394).
- Local unprivileged user holding the tty open can amplify: sustained tcsetattr loop keeps the queue occupied indefinitely; other opens/closes/ioctls of the same tty (single-port device) queue behind.
- DoS only β no memory corruption, no info leak (see negatives).
Family precedent severity: Low (evil-USB precondition or seconds-scale local stall; cf. DF-5421/5424/5425/5449/5456/5476/5487/5496/5504).
Fix validation (family standard)
fix.diff (6 hunks): uark_cfg_write() returns usb_error_t with
UARK_CFG_TIMEOUT (250 ms), uark_cfg_param() aborts the 6-write chain
on the first failed step (5 of 6 steps checked; the final LCR write's
return is irrelevant to chain length). Mechanism-level validation at
runtime by harness phases B (2029 ms) and C (561 ms) vs baseline 6995 ms;
the diff applies 6/6 clean (git apply --check on the audit tree;
patch -p1 on the guest) and the patched production uark.ko compiles
with zero compiler diagnostics (fix_compile.log). Guest /usr/src
uark.c reverted after the compile; guest reset (with-src) performed.
Negative results this pass (pass-2 depth, classes hunted and killed)
- No control-IN reads anywhere:
uark_cfg_writeis the only request helper and is write-only (uark.c:423-427, wLength 0, NULL data) β the DF-1062/2342/5412/5438/5448/5455/5489 short-control-IN uninit class is structurally absent. - Divisor math safe:
data = (3000000 + speed/2)/speedwith speed β [300,115200] (uark.c:343,359) β divisor β [26,10000], fits uint16; LSB/MSB split (uark.c:362-363) matches the ARK3116 protocol (OpenBSD heritage). No B0 branch needed β B0 is rejected in pre_param (no uvscom DF-5447 inversion class). - Bulk endpoint buffer math bounded: write path sends only the
actlenreturned byucom_get_data(..., UARK_BUF_SIZE, &actlen)(uark.c:260-263) β€ bufsize 1024 (uark.c:56,123); read path sets frame len tousbd_xfer_max_lenanducom_put_dataclamps (uark.c:290-294).uint32_t actlenvsint *prototype (uark.c:253, usbdi.h:542) is a benign same-width pun, value β€ 1024. - Status bytes never written:
sc_msr/sc_lsr(uark.c:91-92) are read (uark.c:403-404) but written by nothing β constant zero modem status; functional only (softc is zeroed β no uninit leak; no intr-endpoint short-packet consumption Γ la ulpt DF-5438). - Attach/detach protocol safe: attach error paths fall into
uark_detachβucom_detachno-ops unless ATTACHED (usb_serial.c: 371-372; ucom_attach self-cleans :344-348); teardown order ucom_detach β usbd_transfer_unsetup β lockuninit (uark.c:241-243) is the family-correct sequence. The DF-5320 unplug-UAF is a core usb_serial.c lifetime defect (already filed) β uark adds no per-driver delta. DF-5394 attach-race is a core usb_bus defect, N/A per-driver. - No synchronous EP0 from the bulk callback threads (umcs DF-5382 class absent); error re-submission only clears the device's own pipe stall (uark.c:267-272, 298-303).
- open/close/poll methods: uark registers none beyond the family
standard;
uark_pollβusbd_transfer_poll(sc->sc_xfer, UARK_N_TRANSFER)(uark.c:437-442) is the canonical form.
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 6995 ms to 2029 ms and phase C (abort + 250 ms bound) to 561 ms; the 6-hunk fix.diff (error-returning uark_cfg_write with UARK_CFG_TIMEOUT, chain aborts across the 6-write uark_cfg_param chain) applies 6/6 clean (git apply --check on the audit tree, patch -p1 on the guest) and compiles the patched production uark.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 b3aa7a97591b5fedc3de1fa5dc982c96ff00a9de4d252425bf00fb206caa143e); fix_compile.log (patched production uark.ko, zero compiler diagnostics; the only 'error' string is the literal -Werror flag); run.log phases B/C ELAPSED lines (2029 ms / 561 ms vs baseline 6995 ms).
Confirmed kernel references
- sys/bus/u4b/serial/uark.c:138-148
- sys/bus/u4b/serial/uark.c:140
- sys/bus/u4b/serial/uark.c:343
- sys/bus/u4b/serial/uark.c:348-396
- sys/bus/u4b/serial/uark.c:361-364
- sys/bus/u4b/serial/uark.c:394-395
- sys/bus/u4b/serial/uark.c:398-405
- sys/bus/u4b/serial/uark.c:407-415
- sys/bus/u4b/serial/uark.c:414
- sys/bus/u4b/serial/uark.c:417-435
- sys/bus/u4b/serial/uark.c:429-430
- sys/bus/u4b/serial/uark.c:431-434
- sys/bus/u4b/serial/usb_serial.h:220-221
- sys/bus/u4b/serial/usb_serial.c:322
- sys/bus/u4b/serial/usb_serial.c:394
- 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:757-779
- 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:1357-1420
- sys/bus/u4b/serial/usb_serial.c:1374-1381
- sys/bus/u4b/serial/usb_serial.c:1619-1637
- sys/bus/u4b/serial/usb_serial.c:1626-1628
- sys/bus/u4b/serial/usb_serial.c:1636
- 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_request.c:759-790
- sys/bus/u4b/usb_process.c:259-331
- sys/bus/u4b/usb_process.c:364-393
Detail
Exploit chain
evil adapter (Arkmicro ARK3116 6547:0232) inserted, or any uark device whose EP0 goes silent mid-operation; 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 ~1.0 s uninterruptibly; every tcsetattr adds ~6.1 s while a session holds the tty open (user-amplifiable: sustained loop keeps the tq busy 6.1 s per instance indefinitely; burst ceiling 2 queued instances); TIOCSBRK/TIOCCBRK add 1.0 s per toggle; close(2) blocks SIGKILL-immune behind the queue (measured 6.995 s / 13.012 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: UA5540[A]=921 ms, [2]=6995 ms, [3]=13012 ms (5-burst), [B]=2029 ms (fix abort), [C]=561 ms (fix abort+250ms), plus absolute per-request START timestamps showing every request sleeping its full ~980-1000 ms (e.g. phase A2: 1088/2070/3061/4061/5061/6050 ms) and the phase-A3 coalescing instrumentation (burst of 5 -> 2 param-task executions); run.2.log stability re-run (854/6950/12963/2034/564 ms); build.log (-Werror-clean in-tree kmod build); fix_compile.log (6/6 hunks, patched production uark.ko, zero compiler diagnostics -- only the literal -Werror flag matches 'error').
PoC changes
Seed concept executed as the DF-5496 umct family rig adapted to uark's exact production shapes: 1-request first-open line-state chain (only set_break registered among the line callbacks -- the mask at usb_serial.c:1374-1381 drops DTR/RTS/ring), 6-request tcsetattr chain at ANY accepted rate (B0 rejected in pre_param -- no umct-style no-B0-branch note needed beyond identity across rates), 0-request HUPCL close (set_dtr NULL -> masked, unlike umct's 1-request drop), 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 (uark.c:429-430), real usb_proc machinery, closer mirroring ucom_close's synchronous usb_proc_mwait entered ~100-150 ms into the chain, and the DF-5476 v2 fidelity lesson applied from the start (unique per-request wait channels). One build iteration: v1 had a parenthesized-nested-ternary typo that failed the on-guest -Werror build (build_failed) -- replaced with a static request-name table, rebuilt clean.
Verified recommended fix
Bound the cfg-path control timeout (UARK_CFG_TIMEOUT 250 ms) and abort the uark_cfg_param() 6-write chain on the first failed request via an error-returning uark_cfg_write() (full diff in fix.diff).
Verdict
REPRODUCED on the guest (KLD-harness-with-production-code class; no Arkmicro ARK3116 USB device model exists): every uark cfg-callback step is a synchronous EP0 vendor write via ucom_cfg_do_request with a 1000 ms timeout (uark.c:429-430) whose usb_error_t is discarded at the only site (uark.c:431-434, '(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 uark registers no ucom_cfg_open (:771-778 pause skipped), so first open issues only the coalesced line-state chain whose callback mask contains ONLY break (uark.c:140 registers set_break; set_dtr/set_rts/set_ring NULL, usb_serial.c:1374-1381) = 1 x 1000 ms (uark.c:414); measured closer block 921 ms. Every later tcsetattr re-queues the param task (double-buffered, usb_serial.c:1691-1693): uark_cfg_param chains SIX writes (uark.c:361 reg3=0x83, :362 div LSB, :363 div MSB, :364 reg3=0x03, :394 reg3=0x00, :395 reg3=LCR) + hz/10 core pause (:1636) = ~6.1 s -- IDENTICAL for every accepted rate 300..115200 (uark_pre_param uark.c:343 rejects B0/0 and >115200; there is no B0 branch) -- the heaviest single-rate chain in the audited family short of umcs' 28-request open/close pair (DF-5383) and 3x umct's (DF-5496); measured closer block 6995 ms (one tcsetattr) and 13012 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+955-957; the closer owns 0 requests of its own (HUPCL DTR drop masked by the absent set_dtr, usb_serial.c:694-696 + :1374-1375; no ucom_cfg_close registered). TIOCSBRK/TIOCCBRK cost 1 x 1000 ms per toggle (uark.c:407-415). 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 2029 ms (B), abort+250 ms bound 561 ms (C); patched production uark.ko compiles 6/6 hunks clean.
No comments yet.