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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5449 Β· 15 files
FileTypeDescriptionSize
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.sh
  • build.log, run.log, run.2.log, fix_compile.log
  • fix.diff β€” error propagation + chain abort + 250 ms timeout bound
  • VERDICT.md, manifest.json, verdict.json
VERDICT.md
↓ download raw

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

fixed
baseline reproduced→ patch + rebuild →patched reproduced

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

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.