uvisor cfg_close: one synchronous 1000 ms-timeout EP0 vendor control-IN with discarded error; an evil Handspring/Palm-VID:PID device stretches every close(2) to ~1 s of uninterruptible ucom-taskqueue occupancy and stalls the unplug (hub event thread) drain
Summary
The driver's ONLY cfg-path request - CLOSE_NOTIFICATION at 1000ms with the error discarded behind the uninterruptible close mwait: an evil Visor-VID adapter NAK-ing the data stage makes every close(2) block ~1s SIGKILL-immune and unplug stalls the hub event thread. Weakest member of the family stall set (open is free - cfg_open empty). Harness: closer blocked 1080/1093ms; 250ms-bound fix mechanism 346/363ms.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5487 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.4 KB | β raw | |
| VERDICT.md | β | 4.6 KB | β raw | |
| uvisor_cfgstall_harness.c | β | 8.5 KB | view raw | |
| Makefile | β | 133 B | β download | |
| build.sh | β | 134 B | view raw | |
| run.sh | β | 139 B | view raw | |
| build.log | β | 7.1 KB | view raw | |
| run.log | β | 1.6 KB | view raw | |
| run.2.log | β | 219 B | view raw | |
| fix.diff | β | 1.1 KB | view raw | |
| fix_compile.log | β | 7.2 KB | view raw | |
| env.txt | β | 443 B | view raw | |
| verdict.json | β | 4.8 KB | view raw |
DF-5487 β uvisor cfg_close: one synchronous 1000 ms-timeout EP0 vendor
control-IN with discarded error; an evil Handspring/Palm-VID:PID device
stretches every close(2) to ~1 s of uninterruptible ucom-taskqueue occupancy
The chain (production code)
uvisor has the smallest cfg surface in the ucom serial family
(uvisor.c:224-231): no ucom_cfg_param, no status/line callbacks, and
uvisor_cfg_open() is EMPTY (uvisor.c:519-523). So open(2) costs only the
core hz/10 pause (usb_serial.c:777) and zero EP0 requests, and
tcsetattr costs nothing (core ucom_cfg_param returns before its pause
when the driver callback is NULL, usb_serial.c:1623-1637).
The only EP0 request in the driver's cfg path is the close chain:
| # | task | requests | uvisor.c |
|---|---|---|---|
| 1 | ucom_close() β ucom_shutdown() HUPCL β line-state task |
0 (no callbacks) | usb_serial.c:694-696 |
| 2 | ucom_cfg_close β uvisor_cfg_close β ucom_cfg_do_request(..., buffer, 0, 1000) |
1Γ CLOSE_NOTIFICATION vendor control-IN | 529-545, req at 539-540 |
whose usb_error_t is discarded (DPRINTFN only, uvisor.c:541-544).
ucom_cfg_do_request expands to usbd_do_request_proc(..., timo)
(usb_serial.h:220-221), and close(2) is synchronous against the queue:
ucom_queue_command() does usb_proc_mwait() for ucom_cfg_close
(usb_serial.c:671-672, 955-957) β uninterruptible.
An evil Visor-VID:PID device (HANDSPRING 082d:0100 etc., uvisor.c:248-278) that NAKs the CLOSE_NOTIFICATION data stage makes the request eat its full 1000 ms timeout (usb_request.c:642-644 "WXFER" lksleep loop, 699-704), so:
- every close(2) of /dev/ttyUxx blocks ~1 s, SIGKILL-immune (measured: closer behind a just-issued open: 1080/1093 ms);
- unplug mid-cycle stalls the per-bus USB hub event thread the same
way (
uvisor_detachβucom_detachβusb_proc_drain, usb_serial.c:394), plus the per-device enum lock each request holds (usb_request.c:467); - the block is repeatable at will (open+close cycles), but bounded at ~1 s per cycle β the weakest member of the family (DF-5476 ubsa ~10 s, DF-5456 umoscom ~14 s, DF-5449 uvscom ~6.1 s, all Low).
Evidence class
No Handspring/Palm VID:PID QEMU device model exists β KLD-harness-with-
production-code (same class as DF-5367/5383/5421/5424/5425/5449/5456/
5476): 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 uvisor's production request counts
(0 per open chain, 1 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 # in-tree kmod build at /usr/src/sys/bus/u4b/serial/hx5487, -Werror clean ./run.sh # kldload; dmesg | grep HX5487
Expected baseline: HX5487[A]: closer: unblocked ELAPSED=~1100 ms
(open-chain hz/10 pause + 1Γ1000 ms cfg-close request).
Expected fix mechanism (phase B, 250 ms cfg-path timeout bound):
HX5487[B]: closer: unblocked ELAPSED=~350 ms.
Observed: A = 1080 / 1093 ms; B = 346 / 363 ms (run.log, run.2.log).
Fix
fix.diff bounds the cfg-path control timeout to 250 ms
(UVISOR_CFG_TIMEOUT, same bound as the ubsa DF-5476 / uvscom DF-5449
family fixes). The patched production uvisor.ko compiles -Werror clean
(fix_compile.log).
DF-5487 VERDICT β REPRODUCED (mechanism-level, production machinery)
Finding: uvisor_cfg_close() issues one synchronous EP0 vendor
control-IN (CLOSE_NOTIFICATION) at a 1000 ms timeout with the error
discarded (uvisor.c:529-545), and ucom_close() waits for it
uninterruptibly (usb_proc_mwait, usb_serial.c:671-672 + 955-957).
An evil Handspring/Palm-VID:PID device (or a wedged real one) that NAKs
the vendor data stage stretches every close(2) to ~1 s SIGKILL-immune and
stalls the unplug (hub event thread) drain identically.
Reproduction (KLD-harness-with-production-code, family evidence class)
No QEMU Visor/Palm device model exists (-device usb-serial is a
Netchip 0525:7701 that binds uftdi), so the harness runs the real
in-kernel usb_process machinery β usb_proc_create / usb_proc_msignal
/ usb_proc_mwait / usb_proc_drain (usb_process.c) β that ucom's
synchronous close and detach drain go through, with uvisor's exact
production chain shape (0 requests + core hz/10 pause per open chain;
0-request HUPCL line task + 1Γ1000 ms request per close chain). One
timed-out EP0 request is modeled by its exact production cost
(lksleep("WXFER", N ms), usb_request.c:642-644).
Measured (hz=100 guest, two runs each):
| phase | closer unblocked | expected |
|---|---|---|
| A baseline (production chain) | 1080 / 1093 ms | ~1100 ms |
| B fix mechanism (250 ms bound) | 346 / 363 ms | ~350 ms |
Decisive lines (run.log / run.2.log):
HX5487[A]: closer: unblocked ELAPSED=1080 ms β¦ =1093 ms
HX5487[B]: closer: unblocked ELAPSED=346 ms β¦ =363 ms
Task-level trace shows the exact production ordering: cfg-open task
(EMPTY + hz/10 pause) β line tasks (0 reqs) β cfg-close task (1Γ1000 ms,
"error DISCARDED") with the closer blocked in usb_proc_mwait throughout.
Why this is the production behavior
uvisor_cfg_close()request: uvisor.c:539-540 (ucom_cfg_do_request(..., buffer, 0, 1000)), error path is DPRINTFN-only (uvisor.c:541-544).ucom_cfg_do_request=usbd_do_request_proc(udev, &sc_super->sc_tq, req, ptr, flags, NULL, timo)(usb_serial.h:220-221) β synchronous control transfer on the ucom taskqueue.- A NAK-ing device makes the request eat the full timeout:
usb_request.c:642-644 (
while (usbd_transfer_pending(xfer)) lksleep(xfer, ..., "WXFER", hz)) + 699-704. - close(2) synchronicity: usb_serial.c:671-672 (
if (fn == ucom_cfg_close) usb_proc_mwait(...)), reached from ucom_close() at usb_serial.c:955-957; mwait is cv_wait β uninterruptible, SIGKILL-immune (family-proven in DF-5421/5424/5425/5449/5456/5476). - Unplug: uvisor_detach (uvisor.c:356-368) β ucom_detach β
usb_proc_drain(&ssc->sc_tq)(usb_serial.c:394) waits out the same request on the hub event thread; every request also serializes behind the per-device enum lock (usb_request.c:467).
Severity
Low β consistent with the family (weakest member: ~1 s per close cycle, bounded; requires an evil/malfunctioning Handspring/Palm-VID:PID device physically attached or passed through). No memory corruption, no info leak; impact is bounded uninterruptible taskqueue/thread occupancy (DoS), repeatable per open/close cycle and on unplug.
Fix validation
fix.diff adds UVISOR_CFG_TIMEOUT (250 ms) and uses it for the close
notification. Validated two ways:
1. Harness phase B models the fixed chain: closer ELAPSED drops
1080β346 / 1093β363 ms. The multi-secondβbounded occupancy change is
the fix mechanism working.
2. The fully patched production uvisor.c compiled -Werror clean as a kmod
on the guest (fix_compile.log, RC=0).
Negative results / what was checked and killed
- No DF-5384-class "detach on never-attached" bug:
ucom_detach()early- returns when!(ssc->sc_flag & UCOM_FLAG_ATTACHED)(usb_serial.c:376-377), so uvisor_attach's error-pathuvisor_detach()(uvisor.c:351-353) is safe even whenucom_attach()never ran or failed internally (it callsucom_detach()itself at usb_serial.c:346). - No cfg_param/status/line chains exist to stall (callback table uvisor.c:224-231) β tcsetattr is free for uvisor.
- Guest left clean: harness dirs removed, production uvisor.c untouched (md5 28d0d390d06dd00f9dbfc03106b453a7 before and after).
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 applied to the guest's /usr/src copy: the production uvisor.ko compiled -Werror-clean RC=0 (fix_compile.log); harness phase B models the fixed driver's timeout bound: closer ELAPSED drops from ~1.08-1.09 s to ~0.35-0.36 s across two runs. The full-second uninterruptible occupancy is GONE under the fix mechanism.
fix.diff (sha256 f73a9c3d... is the harness; fix.diff itself verified by git apply --check on host + guest patch -p5); fix_compile.log (RC=0); run.log/run.2.log phase-B lines; VERDICT.md 'Fix validation'.
Confirmed kernel references
- sys/bus/u4b/serial/uvisor.c:529-545
- sys/bus/u4b/serial/uvisor.c:519-523
- sys/bus/u4b/serial/uvisor.c:224-231
- sys/bus/u4b/serial/usb_serial.c:671-672
- sys/bus/u4b/serial/usb_serial.c:955-957
- sys/bus/u4b/serial/usb_serial.c:394
- sys/bus/u4b/serial/usb_serial.h:220-221
- sys/bus/u4b/usb_request.c:642-644
- sys/bus/u4b/usb_request.c:467
Detail
Exploit chain
Evil USB adapter with a Handspring/Palm VID:PID from uvisor_devs[] (e.g. HANDSPRING 082d:0100, uvisor.c:254) enumerates as uvisor and NAKs the CLOSE_NOTIFICATION vendor data stage -> the single cfg-close request eats its full 1000 ms timeout -> every close(2) of /dev/ttyUxx blocks ~1 s SIGKILL-immune behind usb_proc_mwait; yanking the device mid-cycle stalls the per-bus USB hub event thread the same ~1 s via ucom_detach -> usb_proc_drain; repeatable per open/close cycle but bounded at ~1 s -- the weakest member of the family (DF-5476 ~10 s, DF-5456 ~14 s, DF-5449 ~6.1 s, all Low).
Evidence (decisive lines)
run.log/run.2.log: 'HX5487[A]: closer: unblocked ELAPSED=1080 ms' / '1093 ms' (baseline) and 'HX5487[B]: ... ELAPSED=346 ms' / '363 ms' (fix mechanism); task-level trace shows cfg-open (EMPTY, 0 reqs + hz/10 pause) -> line tasks (0 reqs) -> cfg-close (1 x 1000 ms, 'error DISCARDED') with the closer in usb_proc_mwait throughout; build.log: -Werror kmod build; fix_compile.log: patched production uvisor.ko RC=0.
PoC changes
No seed PoC existed; authored from scratch on the DF-5449 harness pattern with uvisor's production chain shape (0 requests per open chain, 1 per close chain, 1000 ms). One bring-up iteration: none needed beyond the pattern (first build -Werror clean).
Verified recommended fix
Bound the cfg-path control timeout to 250 ms (UVISOR_CFG_TIMEOUT) for the CLOSE_NOTIFICATION request (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/5449/5456/5476): uvisor_cfg_close() issues the driver's ONLY cfg-path EP0 request -- one CLOSE_NOTIFICATION vendor control-IN at a 1000 ms timeout with the usb_error_t discarded (uvisor.c:529-545, request at 539-540, error at 541-544) -- and close(2) waits for it uninterruptibly (ucom_queue_command -> usb_proc_mwait, usb_serial.c:671-672 + 955-957). uvisor's cfg surface is the family's smallest: uvisor_cfg_open() is empty (uvisor.c:519-523, open chain = 0 requests + core hz/10 pause usb_serial.c:777) and there are no param/status/line callbacks (uvisor.c:224-231), so tcsetattr is free. Harness on the real usb_process taskqueue: a close(2) issued 50 ms behind an open blocks ELAPSED=1080/1093 ms across two runs (SIGKILL-immune) while the evil Visor NAKs the vendor data stage (WXFER cost, usb_request.c:642-644); 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). Fix mechanism (UVISOR_CFG_TIMEOUT 250 ms bound) demonstrated at ELAPSED=346/363 ms and the fully patched production uvisor.ko compiled -Werror-clean RC=0 on the guest.
No comments yet.