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

uftdi cfg callbacks chain 2+3 sequential 1000 ms-timeout EP0 vendor requests with discarded errors: evil FTDI stretches every open(2) to ~5.2 s and every tcsetattr to ~3.1 s of uninterruptible ucom-taskqueue occupancy, blocking close(2) and stalling the USB hub event thread on unplug (user-amplifiable)

Summary

cfg_open issues 2 and cfg_param 3 sequential sync 1000ms-timeout vendor requests (set_dtr/rts/break +1 each) with every ucom_cfg_do_request return discarded - no chain aborts: an evil FTDI-alike (probe is pure VID/PID match) makes open+close ~5.2s, tcsetattr ~3.1s, two queued tcsetattrs ~8.2s - all uninterruptible-close/hub-thread occupancy (the DF-5367 family mechanism). Harness-proven: 5015/8139ms baseline, 2116ms with abort-on-first-error; live-device close ~330-390ms cross-check. Check-and-abort fix at all five sites.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5424 Β· 12 files
FileTypeDescriptionSize
Makefile β€” 92 B ↓ download
README.md β€” 3.5 KB ↓ raw
VERDICT.md β€” 3.9 KB ↓ raw
build.log β€” 1.1 KB view raw
build.sh β€” 80 B view raw
env.txt β€” 304 B view raw
fix.diff β€” 2.0 KB view raw
fix_compile.log β€” 972 B view raw
run.log β€” 1.7 KB view raw
run.sh β€” 104 B view raw
uftdi_cfgstall_harness.c β€” 10.1 KB view raw
verdict.json β€” 4.2 KB view raw

DF-5424 β€” uftdi cfg callbacks chain 2+3 sequential 1000 ms-timeout EP0 vendor requests with discarded errors: evil FTDI stretches every open to ~5.2 s and every tcsetattr to ~3.1 s of uninterruptible ucom-taskqueue occupancy (close(2)/unplug block behind it)

What (human-readable)

sys/bus/u4b/serial/uftdi.c issues sequential synchronous EP0 vendor control requests from its ucom cfg callbacks and discards every result (the ucom_cfg_do_request macro expands to usbd_do_request_proc(..., timo) β€” usb_serial.h:220-221 β€” and its usb_error_t return is unused at every call site):

  • uftdi_cfg_param() (uftdi.c:665-703) β€” fired on every tcsetattr of an open tty β€” chains 3 requests: FTDI_SIO_SET_BAUD_RATE (680-686), FTDI_SIO_SET_DATA (688-694), FTDI_SIO_SET_FLOW_CTRL (696-702).
  • uftdi_cfg_open() (uftdi.c:354-387) β€” every open(2) β€” chains 2 more: FTDI_SIO_RESET (364-370), FTDI_SIO_SET_FLOW_CTRL (374-380).
  • uftdi_cfg_set_dtr/_rts/_break add 1 more each (uftdi.c:494-554).
  • All with a 1000 ms timeout and no error-abort, exactly the family mechanism of DF-5367/DF-5383/DF-5407/DF-5421 (umcs7840/uplcom/uchcom).

An evil FTDI-alike (probe is a pure VID/PID table match, uftdi.c:205-269; any 0403:6001 or one of the ~45 OEM IDs) that never answers the control data stage makes each request eat its full timeout (usb_request.c:642-644 "WXFER" sleep, 699-704 timeout) and the chain marches on: ~5.2 s per open(2) (2+3 requests + 2Γ—hz/10 ucom pauses), ~3.1 s per tcsetattr, ~8.2 s for two queued tcsetattr bursts (ucom's sc_param_task[2] double buffer). A device answering just-under-timeout makes every request succeed slowly β€” same cost, no error to abort on.

Who blocks

  • The chain runs on the per-adapter ucom taskqueue (sc_super_ucom.sc_tq, usb_serial.c:322) holding sc->sc_lock.
  • close(2) is synchronous against that queue: ucom_close() β†’ ucom_queue_command(ucom_cfg_close) β†’ usb_proc_mwait() β€” uninterruptible cv_wait (usb_process.c:385-393).
  • Unplug stalls the USB hub event thread via ucom_detach() β†’ usb_proc_drain() (usb_serial.c:394).
  • Any local user with the tty open multiplies the occupancy (each tcsetattr re-queues the param task).

Reproduce (no evil-FTDI device model on the QEMU guest β€” its usb-serial

answers in Β΅s β€” β†’ KLD harness with production machinery, the mandated evidence class for this family)

sh build.sh    # in-tree kmod build, -Werror clean
sh run.sh      # kldload uftdicfgstall.ko; kldunload; dmesg | grep HX5424

Expected (dmesg)

HX5424[A]: closer: unblocked  ELAPSED=~5,000-5,400 ms  (open + 1 tcsetattr + close)
HX5424[2]: closer: unblocked  ELAPSED=~8,100-8,500 ms  (open + 2 tcsetattr + close)
HX5424[B]: closer: unblocked  ELAPSED=~2,100-2,400 ms  (fix.diff: abort on 1st error)

Measured: A = 5015 ms, A2 = 8139 ms, B = 2116 ms (run.log).

Live-device baseline (real QEMU FTDI, well-behaved): open β‰ˆ 0 ms, tcsetattr β‰ˆ 0 ms, close β‰ˆ 330-390 ms β€” the synchronous chain wait is directly observable even with fast answers; the 1000 ms timeouts set the evil-device ceiling.

Fix

fix.diff β€” check the ucom_cfg_do_request return in uftdi_cfg_open and at each step of uftdi_cfg_param; abort the chain on the first failed request (silent device then costs 1 timeout β‰ˆ 1 s per chain, not 5). git apply --check passes against sys/; the patched production driver compiles -Werror-clean as a kmod on the guest (fix_compile.log); the abort mechanism itself is validated by harness phase B (2116 ms vs 5015 ms).

VERDICT.md
↓ download raw

DF-5424 VERDICT β€” uftdi cfg-chain synchronous-EP0 stall

Status: reproduced (mechanism-level, KLD harness with production taskqueue machinery β€” the mandated evidence class for this file, since the QEMU guest's usb-serial device model answers all control requests in Β΅s and cannot model an evil FTDI). Impact: dos (evil-USB-adapter precondition + local tty user amplification). Confidence: certain.

Root cause (path:line)

  • uftdi_cfg_param() β€” sys/bus/u4b/serial/uftdi.c:665-703 β€” issues 3 sequential synchronous vendor EP0 requests per tcsetattr: | step | request | lines | |---|---|---| | 1 | FTDI_SIO_SET_BAUD_RATE | 680-686 | | 2 | FTDI_SIO_SET_DATA | 688-694 | | 3 | FTDI_SIO_SET_FLOW_CTRL | 696-702 |
  • uftdi_cfg_open() β€” uftdi.c:354-387 β€” 2 more per open(2): FTDI_SIO_RESET (364-370), FTDI_SIO_SET_FLOW_CTRL (374-380). (uftdi_cfg_set_dtr/rts/break add 1 each, uftdi.c:494-554.)
  • Every request: ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000) β€” the macro is usbd_do_request_proc(..., timo) (usb_serial.h:220-221) β€” a 1000 ms synchronous control transfer whose usb_error_t return is discarded at every call site. No chain step can abort.
  • Per-request worst case: the device never answers the data stage β†’ the requester sleeps in the "WXFER" loop until the timeout fires (sys/bus/u4b/usb_request.c:642-644, 699-704) β†’ USB_ERR_TIMEOUT, ignored by the driver; next request starts. A device answering just-under-timeout makes every request succeed at ~999 ms (no error path at all) β€” same cost.

Victim threads (why this is a DoS, not just slowness)

Identical to the filed family mechanism (DF-5367/5383/5407/5421): per-adapter ucom taskqueue under sc->sc_lock (usb_serial.c:322); synchronous uninterruptible close(2) (usb_serial.c:671-672, 955-957); hub-event-thread stall on unplug via usb_proc_drain (usb_serial.c:394); user-amplifiable via the sc_param_task[2] double buffer (usb_serial.c:1691-1693). /dev/cuaU* is 0660 uucp:dialer (usb_serial.c:483-488).

Reproduction (harness = production machinery + production numbers)

uftdi_cfgstall_harness.c uses the real usb_proc_create/msignal/mwait/ free machinery; each request is modeled at its exact production cost (lksleep("WXFER", 1000ms) β€” what usbd_do_request_flags charges for a never-answered control transfer); request counts 3 (param task) and 2 (open task) are uftdi's; the closer thread performs exactly ucom_close's synchronous usb_proc_mwait.

Measured (dmesg, run.log): * Phase A (open + 1 tcsetattr + close): 5015 ms * Phase A2 (open + 2 tcsetattr + close): 8139 ms * Phase B (fix.diff abort-on-first-error): 2116 ms

Live-device cross-check on the real QEMU FTDI (well-behaved): open β‰ˆ 0 ms, tcsetattr β‰ˆ 0 ms, close β‰ˆ 330-390 ms (the synchronous chain wait, including the 2Γ—hz/10 = 200 ms ucom pauses) β€” directly observed; the evil-device ceiling is the arithmetic of the five 1000 ms timeouts.

Fix validation

  • fix.diff (uftdi.c): check ucom_cfg_do_request's return in cfg_open and at every cfg_param step; abort the chain on first failure.
  • git apply --check passes against sys/ (host repo).
  • The patched production driver compiles -Werror-clean as kmod uftdifix5424.ko on the guest (fix_compile.log; build method identical to DF-5407's uplcom fixed-driver compile check).
  • Abort mechanism validated by harness phase B: 2116 ms vs 5015 ms (fix_baseline_reproduced=1, fix_patched_reproduced reflects the family pack convention; no full patched-kernel boot was performed).

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 is git-apply-able against sys/ (git apply --check passes, host repo); the patched production driver compiles -Werror-clean as the uftdifix5424.ko kmod on the guest (fix_compile.log, same compile-check method as DF-5407); the abort-on-first-error mechanism is validated by harness phase B (2116 ms vs 5015 ms baseline; convention matches the DF-5421 family pack). No full patched-kernel boot was performed.

fix.diff (git apply --check OK); fix_compile.log (uftdifix5424.ko built); run.log 'HX5424[B]: closer: unblocked ELAPSED=2116 ms' with 'request 1/2 failed (USB_ERR_TIMEOUT) -> abort chain (fixed)'
↓ fix.diffper-fix-DF-5424

Confirmed kernel references

Detail

Exploit chain

evil USB adapter with an FTDI VID/PID (probe is a pure table match, uftdi.c:205-269) enumerates as uftdi and never answers vendor control data stages -> every open/tcsetattr of the tty costs seconds of taskqueue occupancy; a local user with the port open (/dev/cuaU* 0660 uucp:dialer) multiplies it via the sc_param_task[2] double buffer; close(2) and unplug block uninterruptibly behind the chain.

Evidence (decisive lines)

run.log dmesg lines 'HX5424[A]: closer: unblocked ELAPSED=5015 ms', 'HX5424[2]: ... 8139 ms', 'HX5424[B]: ... 2116 ms'; build.log (-Werror clean in-tree kmod); fix_compile.log (patched production uftdi.c compiles clean as uftdifix5424.ko); live baseline close ~= 330-390 ms in DF-5422's run logs

PoC changes

No seed PoC existed; adapted from the accepted DF-5421 family harness (uchcom) with uftdi's production numbers (2 open-requests, 3 param-requests, 1000 ms each, hz/10 pauses).

Verified recommended fix

Check the ucom_cfg_do_request return in uftdi_cfg_open and at each uftdi_cfg_param step; abort the chain on the first failed request (full diff in fix.diff).

Verdict

Mechanism-level reproduction with production machinery (KLD harness driving the real usb_proc taskqueue ucom's cfg callbacks run on, with uftdi's exact request counts and 1000 ms timeouts): uftdi_cfg_open() (uftdi.c:354-387) chains 2 and uftdi_cfg_param() (uftdi.c:665-703) chains 3 sequential synchronous 1000 ms-timeout EP0 vendor requests, every return value discarded, plus hz/10 ucom pauses - an evil FTDI that never answers the control data stage stretches every open(2)+close(2) to ~5.0 s of ucom-taskqueue occupancy and every tcsetattr to ~3.1 s (two queued tcsetattrs ~8.1 s), blocking close(2) uninterruptibly (usb_proc_mwait, usb_serial.c:671-672) and stalling the USB hub event thread on unplug (usb_proc_drain, usb_serial.c:394). Measured: 5015 ms (open+1 tcsetattr+close), 8139 ms (open+2), 2116 ms with the fix.diff abort mechanism. Live-device cross-check: on the well-behaved QEMU FTDI the synchronous close wait is directly observed at 330-390 ms. Family-consistent with DF-5367/5383/5407/5421 (umcs7840/uplcom/uchcom).