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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5407 Β· 16 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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:

  1. The production attach chain issues exactly 14 sequential synchronous EP0 control requests β€” proven with the verbatim production code: build.sh awk-extracts uplcom_reset (uplcom.c:460-473), uplcom_pl2303_do (475-495) and uplcom_pl2303_init (497-525) from the guest's clean /usr/src snapshot (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.

  2. 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 to usbd_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.

  1. 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>4 overruns 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.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, 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 by actlen >= 9; INTR transfer bufsize=0 β†’ buffer sized to max_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 to ucom_put_data (core-checked).
  • Attach/detach error paths (uplcom.c:404-458): every goto detach is safe β€” ucom_detach() early-returns when !(sc_flag & UCOM_FLAG_ATTACHED) (usb_serial.c:371-372), so calling it before/after ucom_attach is idempotent; usbd_transfer_unsetup tolerates partial setup; lockuninit runs 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; intr buf[8] β†’ sc_msr is 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; <= 6000000 for HX. A negative/zero c_ospeed would 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_tq under sc_lock (usb_serial.c:291-292, usb_serial.h:221 ucom_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

fixed
baseline reproduced→ patch + rebuild →patched reproduced

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

Confirmed kernel references

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.