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)
PoC verification
Evidence pack
findings/poc/DF-5424 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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/_breakadd 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) holdingsc->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).
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/breakadd 1 each, uftdi.c:494-554.)- Every request:
ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000)β the macro isusbd_do_request_proc(..., timo)(usb_serial.h:220-221) β a 1000 ms synchronous control transfer whoseusb_error_treturn 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): checkucom_cfg_do_request's return in cfg_open and at every cfg_param step; abort the chain on first failure.git apply --checkpasses againstsys/(host repo).- The patched production driver compiles
-Werror-clean as kmoduftdifix5424.koon 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
fixedfix.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)'
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).
No comments yet.