uplcom_attach() pins the per-bus USB explore thread for up to ~70 s per insertion via 14 sequential synchronous 5 s-timeout EP0 vendor requests (uplcom_reset + uplcom_pl2303_init) that an evil PL2303 device stretches by answering each successfully at just-under-timeout
Summary
14 sequential sync control requests at the 5000ms default timeout, each an uninterruptible lksleep, on the shared per-bus explore thread holding the enum lock: an evil PL2303 answering each at ~4.9s defeats the abort-on-error chain and pins ~67s per insertion (largest ucom-family ceiling: uipaq 12.8s, umcs 0.5s-class) - all hotplug AND unplug handling for the bus freezes, repeatable to near-continuity by a self-re-enumerating gadget. Harness-proven with production code verbatim (awk-extracted from the clean guest snapshot): 67337/67320/67337/67369ms pinned across 4 runs with the exact PL2303HX request trace; 250ms-per-request fix caps the chain at ~2.2s (31x reduction). Same code shape upstream in FreeBSD.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5407 Β· 16 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 4.4 KB | β raw | |
| VERDICT.md | β | 7.9 KB | β raw | |
| uplcom_stall_harness.c | β | 6.9 KB | view raw | |
| build.sh | β | 391 B | view raw | |
| run.sh | β | 174 B | view raw | |
| fix.diff | β | 4.7 KB | view raw | |
| uplcom.fixed.c | β | 26.4 KB | view raw | |
| build.log | β | 901 B | view raw | |
| fix_compile.log | β | 6.1 KB | view raw | |
| fix_build.log | β | 6.1 KB | view raw | |
| fix_run.log | β | 1.6 KB | view raw | |
| run.log | β | 7.1 KB | view raw | |
| run.2.log | β | 10.3 KB | view raw | |
| env.txt | β | 338 B | view raw | |
| manifest.json | β | 1.8 KB | view raw | |
| verdict.json | β | 7.0 KB | view raw |
DF-5407 β uplcom attach-time control-request chain stall (14 Γ 5 s)
File: sys/bus/u4b/serial/uplcom.c:404-436 (chain), :460-525 (request bodies)
Severity: Medium (crafted-USB-device local DoS of per-bus USB enumeration)
PoC status: reproduced β KLD-harness-with-production-code evidence class
(no QEMU USB device model can present a PL2303 VID:PID; the emulated
usb-serial is 0525:7701 and binds uftdi, not uplcom).
What the bug is
uplcom_attach() runs 14 sequential synchronous EP0 control requests
on the per-bus USB explore thread before the device finishes attaching:
| # | call | site |
|---|---|---|
| 1 | uplcom_reset() |
uplcom.c:412 β 460-473 |
| 10 | init sequence 0x8484/0x0404/0x8383β¦ | uplcom.c:502-511 |
| 1 | chiptype constant (2/0x44 HX, 2/0x24) |
uplcom.c:514-519 |
| 2 | 8,0,0 and 9,0,0 |
uplcom.c:521-522 |
Every request is usbd_do_request() β USB_DEFAULT_TIMEOUT = 5000 ms
(usbdi.h:87, 511-512; clamped [50,30000] ms at usb_request.c:424-430).
USB puts no protocol bound on how long a device may NAK a control
transfer's data/status stage β the only bound is the host timeout,
enforced at usb_request.c:700-708 while the requester sleeps
uninterruptibly (lksleep(xfer, lock, 0, "WXFER", hz),
usb_request.c:633-636). The ||-chain at uplcom.c:502-511 aborts on the
first error, but a device that succeeds slowly (answers each request
after just under 5 s) keeps the chain going through all 14 requests:
~70 s of explore-thread stall per insertion.
That thread (uhub_explore_sub, usb_hub.c:530/1103 β
usb_probe_and_attach, usb_device.c:1316-1406, enum lock at :1329)
serially handles all hub events for the bus β hotplug and unplug
processing for every other device on the bus freezes with it. A
gadget-class device can drop off the bus and re-enumerate itself
(Facedancer-style), so the stall is repeatable to near-continuity.
This is the same defect family as DF-5359 (uipaq, 12.8 s, 64Γ100 ms), DF-5367 (ufoma, ~16-18 s), DF-5382/DF-5383 (umcs, 500 ms-class chains) β but with the 5 s default timeout, giving the largest per-insertion ceiling of the family. FreeBSD's uplcom has the same code shape (upstream candidate).
Reproduction (KLD harness, production code verbatim)
build.sh extracts uplcom_reset / uplcom_pl2303_do /
uplcom_pl2303_init verbatim from the guest's clean
/usr/src/sys/bus/u4b/serial/uplcom.c (sha256 recorded) and compiles
them into a KLD; only the transport is intercepted β
usbd_do_request()/usbd_do_request_flags() are re-targeted to an
evil-device model that answers every EP0 request successfully after
just-under-timeout ms of NAK-ing (USB-legal), sleeping uninterruptibly
exactly like the production WXFER wait.
# on the guest (files pushed to /root/df5407):
sh /root/df5407/build.sh # also applies fix.diff to a scratch copy and
# compiles the whole patched file -Werror-clean
sh /root/df5407/run.sh # kldload blocks ~70 s in phase A β that IS the bug
Decisive output (3 independent kldload executions, run.log/run.2.log):
HX5407[A]: uplcom_attach chain DONE: 67337 ms pinned, 14 sync EP0 requests HX5407[A]: uplcom_attach chain DONE: 67320 ms pinned, 14 sync EP0 requests HX5407[B]: uplcom_attach chain DONE: 2170 ms pinned, 14 sync EP0 requests <- fix.diff mechanism
Phase A = production code (5 s default timeout): 67.3 s pinned, exactly
the 14-request production sequence (request trace in run.log shows the
0x8484/0x0404/0x8383 dance, 2/0x44 for PL2303HX, 8/9). Phase B =
same functions extracted from the fix.diff-patched file (250 ms bound
per request): 2.2 s β a 31Γ reduction of the stall ceiling.
Side observation recorded by the harness: every request had
wLength β€ 1 β€ sizeof(buf[4]) (0 overruns) β the buf[4] stack buffer in
uplcom_pl2303_do (uplcom.c:481) is NOT overflown by this path.
Fix
fix.diff β bound the attach-path requests to 250 ms each
(UPLCOM_INIT_TIMEOUT via usbd_do_request_flags()), exactly the
mechanism already applied to umcs (DF-5382/5383). A healthy PL2303
answers these vendor requests in well under a millisecond; 250 ms keeps
two orders of magnitude of margin while capping the whole attach chain at
3.5 s worst-case. Validated: git-apply-able, compiles -Werror-clean as a
kmod (fix_compile.log), and the patched chain measures 2.2 s in phase B.
DF-5407 β VERDICT
Status: reproduced (impact: dos, confidence: certain)
Evidence class: KLD-harness-with-production-code β prescribed for this
file because the QEMU guest's emulated usb-serial device is 0525:7701
(binds uftdi, per DF-5359's finding) and no QEMU USB device model can
present a PL2303 VID:PID from uplcom_devs[] (uplcom.c:238-298).
What was verified, and how honestly
The claim has three parts; each was proven at a different level:
-
The production attach chain issues exactly 14 sequential synchronous EP0 control requests β proven with the verbatim production code:
build.shawk-extractsuplcom_reset(uplcom.c:460-473),uplcom_pl2303_do(475-495) anduplcom_pl2303_init(497-525) from the guest's clean/usr/srcsnapshot (SHA256 4ac9bb391b8968275a28011881e6c490273d05c147c390bd6d028fe57618ecb9 == the audit tree's file) and compiles them, unmodified, into the harness KLD. The runtime request trace (run.log) shows the exact documented sequence: reset, 0x8484/0x0404/0x8383Γ2, 0x0404/1, 0x8484, 0x8383, 0/1, 1,2/0x44(PL2303HX branch, uplcom.c:515),8,9β 14 requests. -
An in-spec-but-slow device pins the calling thread ~70 s β proven for the thread that runs kldload, and by source citation for the thread that runs attach: only the transport was intercepted;
usbd_do_request()resolves tousbd_do_request_flags(..., USB_DEFAULT_TIMEOUT=5000)(usbdi.h:511-512, :87). The evil-device model answers each request successfully after 4900 ms of NAK β USB 2.0 allows NAK-ing a control data/status stage indefinitely; the only bound is the host's 5000 ms timeout (checked at usb_request.c:700-708), and the requester waits uninterruptibly (lksleep(..., 0, "WXFER", hz), usb_request.c:633-636) β the harness mirrors that uninterruptible sleep. Measured, 3 independent executions: 67337 / 67320 / 67337-class ms (deterministic: 14 Γ 4900 ms minus tick slack), and the kldload(2) syscall itself did not return for that whole window β a user-visible, uninterruptible freeze of the loading thread. At real attach time the caller is the per-bus USB explore thread:uhub_explore_sub(usb_hub.c:530, called from the explore loop at :1103) βusb_probe_and_attach(usb_device.c:1316, enum lock :1329, driver attach :1406). That thread serially processes all hub events for the bus β the 67 s stall freezes hotplug and unplug handling for every device on the bus. This thread-context part is source-cited (same citations accepted for DF-5359/5367/5383), not executed β that is the honest boundary of the harness: the device-behavior premise (slow-but-successful control responses) is USB-spec-legal and is the same premise already verified for the umcs/uipaq siblings.
Why "slow success" and not "timeout": a device that simply stalls/NAKs
past the timeout fails request #1 and the || chain (uplcom.c:502-511)
aborts after one 5 s timeout. The amplifier requires answering each
request successfully at 4.9 s β entirely in-spec, and trivially
scriptable on a Facedancer/gadget device. A gadget can also electrically
drop and re-present itself, repeating the 67 s stall per enumeration.
- Impact ceiling β bounded per insertion (~70 s + attach overhead),
repeatable via (self-)replug to near-continuous enumeration denial for
the bus; includes delayed unplug processing (a wedged evil device can't
even be cleaned up while the thread is stuck). No memory corruption: no
OOB anywhere on this path (every request's wLength β€ 1 β€ 4; harness
counted 0
wLength>4overruns across all runs).
Numbers (run.log / run.2.log, 3 kldload executions)
| phase | code | per-req timeout | measured |
|---|---|---|---|
| A | production uplcom.c | 5000 ms (default) | 67337 / 67320 ms, 14 reqs |
| B | fix.diff-patched | 250 ms (UPLCOM_INIT_TIMEOUT) | 2170 / 2204 ms, 14 reqs |
Fix validation
fix.diffis git-apply-able against the pristine tree (git apply --checkon host;patch -p1on the guest's clean/usr/srccopy β 5 hunks OK, logged in build.log).- The whole patched file compiles
-Werror-clean as a kmod (uplcomfix5407.ko, fix_compile.log / fix_build.log). - The patched production functions (extracted from the patched file) were executed in harness phase B: stall 67.3 s β 2.2 s (31Γ).
- Scope caveat: mechanism + compile level; an end-to-end replug test with a physical crafted PL2303 device is not possible on this guest. No full kernel rebuild was performed (the finding is a bounded DoS, not memory corruption; phase B exercises the actual patched code).
Negative results (classes hunted and killed, pass 2)
- OOB write
buf[4]in uplcom_pl2303_do (uplcom.c:481,489): all call sites pass length β {0,1} (uplcom.c:502-522); the HCI data stage is bounded by wLength (usb_request.c:566-614). Harness counted 0 overruns. - OOB read
buf[9]in uplcom_intr_callback (uplcom.c:779-792): copy guarded byactlen >= 9; INTR transferbufsize=0β buffer sized tomax_frame_size(usb_transfer.c:646-653), actlen β€ frame len. - Bulk callbacks (uplcom.c:826-886): write path asks ucom core for at
most
UPLCOM_BULK_BUF_SIZE=1024=bufsize (uplcom.c:194,838-839); read path hands actlen β€ max_data_length toucom_put_data(core-checked). - Attach/detach error paths (uplcom.c:404-458): every
goto detachis safe βucom_detach()early-returns when!(sc_flag & UCOM_FLAG_ATTACHED)(usb_serial.c:371-372), so calling it before/afterucom_attachis idempotent;usbd_transfer_unsetuptolerates partial setup;lockuninitruns exactly once (newbus does not re-detach after failed attach; the driver self-detaches at uplcom.c:441-443). - Device-supplied data feeding counts/sizes/tty: chiptype
(uplcom.c:360-366) selects constants only;
sc_data_iface_no(uplcom.c:395) is only ever a wIndex byte; intrbuf[8]βsc_msris masked into three SER_* bits into a zeroed byte (uplcom.c:796-807); no device value ever sizes a copy. - pre_param/baud (uplcom.c:607-637): fixed table for PL2303;
<= 6000000for HX. A negative/zeroc_ospeedwould pass the HX check and be sent as a garbage divisor (USETDW, uplcom.c:650) β 4-byte store into a kernel struct, device-side effect only; hardening nit, not a finding. (ucom core normalizes B0 before the driver param path.) - init-after-ucom_attach window (uplcom.c:424-433): /dev/cuaU* exists
for a few-hundred-ms before init; if init then fails, detach β
ucom_drain()(usb_serial.c:420-428) waits for open fds β an indefinite hang needs a cooperating local process holding the fd open; the drain itself is ucom-core behavior already covered by the DF-5320 family. Noted, not filed separately. - Locking: cfg callbacks are serialized on
sc_tqundersc_lock(usb_serial.c:291-292, usb_serial.h:221ucom_cfg_do_request); the intr/bulk callbacks take no cfg-path locks themselves. No AB-BA found.
Severity calibration
DF-5359 (uipaq, 12.8 s/insertion) = Low; DF-5382 (umcs, continuous) = Medium. This one: ~67 s/insertion β 5Γ uipaq β and repeatable to near-continuity by a self-re-enumerating gadget, with unplug processing frozen too. Filed Medium (CVSS 3.1: AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L β physical-access local DoS).
Environment
Guest: DragonFly dfbsd 6.5-DEVELOPMENT #0 (X86_64_GENERIC), KVM,
post-run vm.sh reset with-src (guest_dirty=0). Full env in env.txt.
Harness source sha256:
d186bb71a982ca259035576291fdfb55ca1526198d2859ee4dc74dc551ce8617.
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 the pristine tree (git apply --check on host; patch -p1 on the guest's clean /usr/src copy, 5 hunks OK in build.log); the WHOLE patched file compiles -Werror-clean as a kmod (uplcomfix5407.ko, fix_compile.log/fix_build.log); harness phase B executes the production functions extracted from the PATCHED file and measures 67.3 s -> 2.2 s (31x reduction of the stall ceiling, deterministic 14x150 ms). Scope caveat: mechanism + compile level; end-to-end with a physical crafted PL2303 device is not possible on this guest; no full kernel rebuild (DoS-class finding, not memory corruption).
fix.diff (sha256 5c62598ea2962ab96b2e602c452e91c1e9526e009359b8a30030d681a5117b4d); fix_compile.log/fix_build.log (uplcomfix5407.ko, -Werror clean); fix_run.log + run.log 'HX5407[B]: uplcom_attach chain DONE: 2170/2204/2183 ms' vs '[A]: 67337/67320/67337/67369 ms'; VERDICT.md 'Fix validation' section
Confirmed kernel references
- sys/bus/u4b/serial/uplcom.c:404-436
- sys/bus/u4b/serial/uplcom.c:460-473
- sys/bus/u4b/serial/uplcom.c:475-495
- sys/bus/u4b/serial/uplcom.c:497-525
- sys/bus/u4b/serial/uplcom.c:502-511
- sys/bus/u4b/usbdi.h:87
- sys/bus/u4b/usbdi.h:511-512
- sys/bus/u4b/usb_request.c:424-430
- sys/bus/u4b/usb_request.c:633-636
- sys/bus/u4b/usb_request.c:700-708
- sys/bus/u4b/usb_device.c:1316-1406
- sys/bus/u4b/usb_hub.c:530
- sys/bus/u4b/usb_hub.c:1103
Detail
Exploit chain
physical/evil-adapter: (1) scriptable USB device (Facedacer/gadget) presents a VID:PID from uplcom_devs[] (e.g. 067b:2303, uplcom.c:271) with device descriptor bDeviceClass!=0x02 and bMaxPacketSize==0x40 -> TYPE_PL2303HX (uplcom.c:360-366); (2) its EP0 answers each of the 14 vendor requests successfully after ~4.9 s of NAK (USB-legal); (3) the per-bus explore thread stays inside uplcom_attach for ~67 s, freezing enumeration and unplug handling bus-wide; (4) the gadget electrically re-enumerates itself and repeats -> near-continuous USB enumeration DoS. No privilege-escalation component exists in this bug class (bounded DoS only).
Evidence (decisive lines)
run.log + run.2.log: 'HX5407[A]: uplcom_attach chain DONE: 67337/67320/67337/67369 ms pinned, 14 sync EP0 requests' and '[B]: 2170/2204/2183 ms' (fix mechanism); full 14-request EP0 trace with type/req/val/idx/len per request (wLength<=1, 0 overruns); build.log: verbatim-extraction sha256s + -Werror kmod build; fix_compile.log: whole fix.diff'd file compiles clean as uplcomfix5407.ko; VERDICT.md: full narrative + negative-results sweep
PoC changes
no seeded PoC existed (pass-2 finding, first evidence pack); built from scratch: KLD harness that awk-extracts the production uplcom_reset/uplcom_pl2303_do/uplcom_pl2303_init VERBATIM from the guest's clean /usr/src snapshot (with the 'static usb_error_t'/'static int' return-type lines via look-behind) and re-targets only usbd_do_request()/usbd_do_request_flags() to an evil-device model (successful answer at timeout-100 ms, uninterruptible WXFER-mirroring sleep). Build iterations fixed: missing module-dir mkdir, usb_process.h include order (needs sys/condvar.h), awk -v regex mangling (switched to index()), return-type line extraction, and UPLCOM_INIT_TIMEOUT extraction from the fixed copy.
Verified recommended fix
Bound the 14 attach-path control requests to 250 ms each (UPLCOM_INIT_TIMEOUT passed via usbd_do_request_flags in uplcom_reset/uplcom_pl2303_do) instead of the 5000 ms USB_DEFAULT_TIMEOUT, capping the whole attach chain at 3.5 s worst-case (fix.diff, mechanism identical to DF-5382/5383's umcs fix)
Verdict
REPRODUCED with the production code verbatim: uplcom_attach() chains 14 sequential synchronous EP0 control requests (1x uplcom_reset at uplcom.c:412 + 13x uplcom_pl2303_init at uplcom.c:433/497-525), each at the 5000 ms USB_DEFAULT_TIMEOUT (usbdi.h:87,511-512). USB allows a device to NAK a control data/status stage arbitrarily long; the host's only bound is that timeout, enforced while the requester sleeps uninterruptibly (usb_request.c:633-636,700-708). The harness awk-extracted the three production functions UNMODIFIED from the guest's clean /usr/src snapshot and ran them against an evil-device model answering every request successfully at just-under-timeout: the attach chain pinned the calling thread for 67337/67320/67337/67369 ms across 4 executions (deterministic 14x4900 ms), with kldload(2) itself blocked uninterruptibly for the whole window; the request trace matches the documented PL2303HX sequence exactly (14 requests, incl. 2/0x44 at uplcom.c:515). At real attach time this chain runs on the per-bus USB explore thread (uhub_explore_sub usb_hub.c:530/1103 -> usb_probe_and_attach usb_device.c:1316-1406 under the enum lock), so one evil adapter freezes hotplug AND unplug processing for every device on the bus for ~67 s per insertion, repeatable by a self-re-enumerating gadget. Not memory corruption: every request's wLength <= 1 <= sizeof(buf[4]) (0 overruns counted). Honest boundary: the explore-thread context is source-cited (same citations accepted for DF-5359/5367/5383), not hardware-executed; no QEMU USB model can present a PL2303 VID:PID (emulated usb-serial is 0525:7701 -> uftdi), hence the prescribed KLD-harness-with-production-code class.
No comments yet.