uvscom cfg callbacks chain sequential 1000 ms-timeout EP0 vendor requests with discarded errors: an evil SUNTAC-VID:PID device stretches the open cycle to ~4.1 s and open+close to ~6.1 s of uninterruptible ucom-taskqueue occupancy, blocking close(2) SIGKILL-immune and stalling the USB hub event thread on unplug
Summary
Every cfg callback's vendor request is 1000ms-timeout with the error discarded: open chains READ_STATUS + hz/10 + set_dtr + set_rts + set_break = 4x1000ms + 100ms of taskqueue occupancy for a silent device; close adds 2 more and waits uninterruptibly. Harness on the production usb_process machinery: closer blocked 5932/5929ms baseline, 1285/1277ms with the abort+250ms fix. The smallest chain of the cfg-stall family (DF-5367/5383/5421/5424/5425).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5449 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.4 KB | β raw | |
| VERDICT.md | β | 5.2 KB | β raw | |
| uvscom_cfgstall_harness.c | β | 9.8 KB | view raw | |
| Makefile | β | 133 B | β download | |
| build.sh | β | 145 B | view raw | |
| run.sh | β | 155 B | view raw | |
| build.log | β | 7.1 KB | view raw | |
| run.log | β | 1.5 KB | view raw | |
| run.2.log | β | 184 B | view raw | |
| fix_compile.log | β | 7.2 KB | view raw | |
| fix_run.log | β | 269 B | view raw | |
| fix.diff | β | 1.9 KB | view raw | |
| env.txt | β | 454 B | view raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 4.8 KB | view raw |
DF-5449 β uvscom cfg callbacks chain sequential 1000 ms-timeout EP0 vendor
requests with discarded errors; evil SUNTAC-VID:PID device stretches the
open+close cycle to ~6.1 s of uninterruptible ucom-taskqueue occupancy
The chain (production code)
Every uvscom cfg callback issues synchronous vendor EP0 requests via
ucom_cfg_do_request(..., 1000) and discards the error
(uvscom.c:706-711, 727-732 β DPRINTFN(0, "... (ignored)")):
open(2) of /dev/ttyU0.* queues on the per-adapter ucom taskqueue
(sc_super_ucom.sc_tq, usb_serial.c:322), in order:
| # | task | requests | uvscom.c |
|---|---|---|---|
| 1 | ucom_cfg_open β uvscom_cfg_open β uvscom_cfg_read_status |
1Γ READ_STATUS (UT_READ_VENDOR_DEVICE) | 634-641, 714-734 |
| β | core pause hz/10 after cfg_open |
β | usb_serial.c:777 |
| 2 | ucom_cfg_line_state β uvscom_cfg_set_dtr + uvscom_cfg_set_rts (ucom_open:874 ucom_modem(tp, SER_DTR|SER_RTS, 0)) |
2Γ LINE_CTL writes | 470-482, 484-497 |
| 3 | ucom_cfg_line_state β uvscom_cfg_set_break (ucom_open:878 ucom_break(sc, 0)) |
1Γ LINE_CTL write | 499-512 |
= 4 Γ 1000 ms + 100 ms β 4.1 s taskqueue occupancy per open with a device that NAKs/stalls every vendor data stage (each request eats its full 1000 ms timeout, usb_request.c:642-644 "WXFER" lksleep + 699-704).
close(2) is synchronous against the same queue: ucom_close() β
ucom_shutdown() (HUPCL default-on) queues a line-state task β
uvscom_cfg_set_dtr(0) = 1 write (usb_serial.c:694-696 β uvscom.c:481), then
queues ucom_cfg_close β uvscom_cfg_write(UVSCOM_SHUTDOWN) = 1 write
(uvscom.c:643-651) and usb_proc_mwait()s uninterruptibly
(usb_serial.c:671-672, 955-957; usb_process.c cv_wait). A close(2) issued
right after open(2) therefore blocks ~4100 + 2000 = ~6.1 s, SIGKILL-immune.
Unplug mid-cycle stalls the USB hub event thread identically via
uvscom_detach() β ucom_detach() β usb_proc_drain() (usb_serial.c:394),
and every request additionally serializes behind the per-device enum lock
(usb_request.c:467) the unplug path needs.
(The tcsetattr chain β uvscom_cfg_param, 2 more writes β is currently dead
because of the DF-5447 pre_param inversion; with that fixed it adds ~2.1 s
per tcsetattr, user-amplifiable via the double-buffered sc_param_task[2].)
Evidence class
No SUNTAC VID:PID QEMU device model exists β KLD-harness-with-production-
code (same class as DF-5367/5383/5421/5424/5425): the harness drives the
real in-kernel usb_process machinery (usb_proc_create / msignal / mwait /
drain) that ucom's synchronous close and detach drain go through, with
uvscom's production request counts (4 per open chain, 2 per close chain) and
1000 ms timeout. One timed-out EP0 request is modeled by its exact production
cost: lksleep("WXFER", 1000 ms) (usb_request.c:642-644).
Reproduce
./build.sh # cd /usr/src/sys/bus/u4b/serial/hx5449 && make ./run.sh # kldload; dmesg | grep HX5449
Expected baseline: HX5449[A]: closer: unblocked ELAPSED=~6100 ms
(open chain 4Γ1000+100 + close chain 2Γ1000).
Expected fix mechanism (phase B, abort-on-first-error + 250 ms timeout):
~1350 ms.
Files
uvscom_cfgstall_harness.c,Makefile,build.sh,run.shbuild.log,run.log,run.2.log,fix_compile.logfix.diffβ error propagation + chain abort + 250 ms timeout boundVERDICT.md,manifest.json,verdict.json
DF-5449 VERDICT β uvscom cfg-chain synchronous EP0 stall (family class)
Status: REPRODUCED (production taskqueue machinery + production request counts/timeouts) β impact: local DoS (uninterruptible close(2), hub-event- thread stall on unplug); fix mechanism validated + patched module compiled clean.
What was run
KLD-harness-with-production-code, identical evidence class to the proven
family findings DF-5367/5383/5421/5424/5425 (no SUNTAC VID:PID device model
exists for QEMU). The harness drives the real in-kernel usb_process
machinery (usb_proc_create/msignal/mwait) β the same taskqueue
(sc_super_ucom.sc_tq, usb_serial.c:322) that ucom's cfg callbacks run on
and that ucom_close()'s synchronous usb_proc_mwait()
(usb_serial.c:671-672, 955-957) and ucom_detach()'s usb_proc_drain()
(usb_serial.c:394) block on. Each timed-out EP0 vendor request is modeled by
its exact production cost: an lksleep("WXFER", 1000 ms)
(usb_request.c:642-644 + 699-704 β what a silent evil device that never
answers the data stage costs).
The production chain being modeled (uvscom.c)
open(2), first open of the tty, queues in order:
1. uvscom_cfg_open β uvscom_cfg_read_status β 1Γ EP0 READ_STATUS,
1000 ms timeout (uvscom.c:634-641, 714-734)
2. core pause hz/10 (usb_serial.c:777)
3. uvscom_cfg_set_dtr + uvscom_cfg_set_rts β 2Γ EP0 LINE_CTL writes
(uvscom.c:470-497; queued by ucom_open:874 ucom_modem(DTR|RTS))
4. uvscom_cfg_set_break β 1Γ EP0 LINE_CTL write (uvscom.c:499-512;
queued by ucom_open:878 ucom_break(sc,0))
= 4 Γ 1000 ms + 100 ms β 4.1 s of uninterruptible taskqueue occupancy per
open. Errors from every request are discarded (uvscom.c:708-711,
729-732: "...(ignored)"), so the chain marches on through timeouts.
close(2) is synchronous against that queue: ucom_shutdown() HUPCL β
uvscom_cfg_set_dtr(0) 1 write (usb_serial.c:694-696), then cfg_close β
uvscom_cfg_write(UVSCOM_SHUTDOWN) 1 write (uvscom.c:643-651) with
usb_proc_mwait() β uninterruptible. Unplug mid-cycle stalls the USB hub
event thread the same way plus behind the per-device enum lock every request
holds (usb_request.c:467).
(The tcsetattr chain β 2 more writes β is currently dead due to the DF-5447 pre_param inversion; this harness models the open+close cycle that IS reachable today. With DF-5447 fixed, each tcsetattr adds ~2.1 s, user-amplifiable via the double-buffered sc_param_task[2].)
Baseline (run.log, run.2.log)
HX5449[A]: closer: HUPCL line task + queue cfg_close + usb_proc_mwait()
(uninterruptible, usb_serial.c:671-672) behind the open chain
HX5449[A]: closer: unblocked ELAPSED=5932 ms (run 1)
HX5449[A]: closer: unblocked ELAPSED=5929 ms (run 2)
β 5.9-6.1 s of SIGKILL-immune block for a close(2) racing an open(2) (hz=100 tick rounding explains the delta from the theoretical 6100 ms). Task-level traces in the log show the exact production shape: cfg-open(READ_STATUS) β line DTR+RTS β line BREAK β HUPCL β cfg-close.
Fix validation
fix.diff (error propagation: uvscom_cfg_write returns usb_error_t;
uvscom_cfg_param aborts after the first failed request; cfg-path control
timeout bounded 1000 β 250 ms):
- Applied to the guest's /usr/src copy (on top of the DF-5447 fix, hunks offset cleanly), the production uvscom.ko module compiled -Werror clean, RC=0 (fix_compile.log).
- Harness phase B models the fixed driver (abort-on-first-error + 250 ms):
HX5449[B]: closer: unblocked ELAPSED=1285 ms (run 1) HX5449[B]: closer: unblocked ELAPSED=1277 ms (run 2)
β 1.3 s vs β 5.9 s baseline β the previously-observed multi-second uninterruptible occupancy collapses. Baseline vs patched both recorded.
Honest scope notes
- The harness models the device-side timing (a request the device never answers) because no SUNTAC hardware model exists; everything on the kernel side (taskqueue, double-buffered messages, uninterruptible mwait, drain semantics, production request counts and timeouts) is the real in-kernel production code path.
- The closer's HUPCL task uses a dedicated message buffer; production re-uses the line-state double buffer, which can merge it with a still- pending open task (shortening the worst case to ~5.1 s). The recorded 5.9 s is the unmerged worst case.
- Severity Low, consistent with the family (DF-5424 uftdi ~5.2 s open / DF-5425 uslcom ~8.2 s close, both Low): exposure requires a malicious or malfunctioning USB device with a SUNTAC VID:PID, or physical possession of an adapter, and the block is bounded (~6.1 s per open+close cycle).
Environment
env.txt: DragonFly 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026, X86_64_GENERIC, cc 8.3. Guest left pristine (uvscom.c md5 a58a99ef02d63a2d2e7f7b661f5224b3 restored; harness dirs and scratch removed; no leftover KLDs β kldstat shows only kernel + ehci + xhci).
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
fixedfix.diff (plus the DF-5447/5448 fixes) applied to the guest's /usr/src copy: the production uvscom.ko kmod compiled -Werror-clean RC=0 (fix_compile.log); harness phase B models the fixed driver's chain collapse: closer ELAPSED drops from ~5.9 s to ~1.28 s across two runs. The previously-observed multi-second uninterruptible occupancy is GONE under the fix mechanism.
fix.diff (sha256 bd5b90f6...); fix_compile.log (RC=0); fix_run.log ('HX5449[B]: closer: unblocked ELAPSED=1285/1277 ms'); VERDICT.md 'Fix validation'.
Confirmed kernel references
Detail
Exploit chain
Evil USB device with a SUNTAC VID:PID from uvscom_devs (uvscom.c:231-242) enumerates as uvscom and never answers vendor control data stages -> every request eats its full 1000 ms timeout -> open(2) cycle = ~4.1 s of sc_tq occupancy; a dialer-group user's close(2) right behind it blocks uninterruptibly ~6.1 s; repeated open/close cycles keep the taskqueue and the closer serialized; unplug mid-cycle stalls the per-bus USB hub event thread the same way.
Evidence (decisive lines)
run.log/run.2.log: 'HX5449[A]: closer: unblocked ELAPSED=5932 ms' / '5929 ms' (baseline) and 'HX5449[B]: ... ELAPSED=1285 ms' / '1277 ms' (fix mechanism); task-level traces show cfg-open(READ_STATUS) -> line DTR+RTS -> line BREAK -> HUPCL -> cfg-close; fix_compile.log: patched production uvscom.ko RC=0 -Werror-clean.
PoC changes
No seed PoC existed; authored from scratch on the DF-5421 harness pattern with uvscom's production chain shape (4 open / 2 close requests, 1000 ms). Two bring-up fixes: (1) -Werror distinct-pointer-type on the line-message discrimination -> explicit casts; (2) the closer's HUPCL task uses a dedicated message buffer so the unmerged (worst-case, longest) production count is modeled -- noted in VERDICT.md that production's shared double-buffer can merge it down to ~5.1 s.
Verified recommended fix
Propagate usb_error_t through uvscom_cfg_write, abort the cfg chains on the first failed request, and bound the cfg-path control timeout to 250 ms (full diff in fix.diff).
Verdict
Mechanism-level reproduction with production machinery (same evidence class as the proven family findings DF-5367/5383/5421/5424/5425): uvscom's cfg callbacks chain sequential synchronous EP0 vendor requests whose errors are discarded (uvscom.c:706-711, 727-732 '(ignored)'), 4 x 1000 ms + hz/10 per open chain (READ_STATUS uvscom.c:634-641/714-734; set_dtr+set_rts uvscom.c:470-497; set_break uvscom.c:499-512) and 2 x 1000 ms per close chain (HUPCL DTR drop via usb_serial.c:694-696 -> uvscom.c:481; UVSCOM_SHUTDOWN uvscom.c:643-651). Harness on the real usb_process taskqueue: a close(2) (usb_proc_mwait, uninterruptible, usb_serial.c:671-672/955-957) racing an open(2) blocks ELAPSED=5932/5929 ms across two runs (SIGKILL-immune); unplug stalls the hub event thread identically via usb_proc_drain (usb_serial.c:394) plus the per-device enum lock (usb_request.c:467). The tcsetattr chain (2 more writes, uvscom.c:581/614) is currently dead due to the DF-5447 pre_param inversion -- with that fixed it adds ~2.1 s per tcsetattr, user-amplifiable via the double-buffered sc_param_task[2]. Fix mechanism (abort chain on first failed request + 250 ms cfg-path timeout bound) demonstrated at ELAPSED=1285/1277 ms and the fully patched production uvscom.ko compiled -Werror-clean RC=0 on the guest.
No comments yet.