uipaq_attach() stalls the per-bus USB explore thread up to ~12.8s on an unresponsive crafted device: 64 unconditional retries of the magic SET_CONTROL_LINE_STATE request (100ms timeout + ~100ms pause each) with no early-out on stall or device-removal, while holding the device enum lock
Summary
The WinCE warm-up loop retries a class request 64x with unconditional hz/10 pauses - an EP0-NAKing crafted device matching any of the ~300 VID:PID pairs forces ~12.8s (STALL/yanked still ~6.4s of pure pause) of blocking on the shared per-bus explore thread while holding the enum lock: one crafted gadget delays enumeration and detach processing for every other device on the bus, repeatable via replug. uipaq.ko auto-loads on the stock guest. Identical loop upstream (FreeBSD/OpenBSD). Hardware-gated (no QEMU device model can present a uipaq VID:PID - verified). Retry-only-on-timeout + 16-iteration fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5359 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.8 KB | β raw | |
| VERDICT.md | β | 2.9 KB | β raw | |
| fix.diff | β | 1.1 KB | view raw | |
| verdict.json | β | 4.3 KB | view raw | |
| manifest.json | β | 1.1 KB | view raw |
DF-5359 β uipaq attach-time USB-explore-thread stall (64 Γ (100ms + 100ms))
File: sys/bus/u4b/serial/uipaq.c:1129-1136
Severity: Low (crafted-USB-device, bounded multi-second DoS of USB enumeration)
PoC status: untested β see VERDICT.md for why this is honestly untestable on
the project's QEMU rig (no QEMU USB device model can present a uipaq-table
VID:PID; QEMU 7.2 exposes no vendorid/productid override for usb-net or
usb-storage, and the attachable usb-serial device is 0525:7701 which binds
uftdi, not uipaq).
What the bug is
uipaq_attach() sends a "magic" UCDC_SET_CONTROL_LINE_STATE control request
up to 64 times before setting up transfers and attaching the ucom:
for (i = 0; i != 64; i++) {
error = usbd_do_request_flags(uaa->device, NULL, &req,
NULL, 0, NULL, 100); /* 100 ms request timeout */
if (error == 0)
break;
usb_pause_mtx(NULL, hz / 10); /* ~100 ms pause, unconditional */
}
- Device present but NAK-ing the request (crafted gadget, or wedged HW): 64 Γ (100 ms timeout + ~100 ms pause) β 12.8 s.
- Device that STALLs the request or is already gone (yanked mid-attach): requests fail fast but the pause still runs 64 Γ ~100 ms β 6.4β7 s.
This loop runs inside usb_probe_and_attach() (sys/bus/u4b/usb_device.c:1286,
called from uhub_explore_sub()), i.e. on the kernel USB explore thread that
serially handles all hub events for the bus, while holding the device's
enum lock (usbd_enum_lock, usb_device.c:1324). One crafted device therefore
delays enumeration/detach of every other device on that bus for up to ~13 s
per insertion, repeatably (replug).
How a human could reproduce it (needs real HW, not QEMU)
- Take a scriptable USB device (e.g. Facedancer21/USBProxy, or a gadget-mode
dev board) and present VID:PID from
uipaq_devs[]β e.g.0x049f:0x0032(Compaq iPAQ USB Sync, uipaq.c:300). - Configure its EP0 to NAK (or STALL) the
SET_CONTROL_LINE_STATEclass-interface request (bmRequestType 0x21, bRequest 0x22). - Plug it into a DragonFly host (uipaq.ko auto-loads β the module is built and installed: sys/bus/u4b/serial/uipaq/Makefile; /boot/kernel/uipaq.ko exists on the audit guest).
- Expected: ~6β13 s gap on the serial console between the uhub attach line
and
uipaq0attach/transfer-setup messages; hotplug events for other devices on the same bus are delayed by the same amount.
Fix
See fix.diff β retry only while the request merely times out (the real
"WinCE device warming up" case), give up immediately on stall/gone, and cap
the retry count so the worst case is ~3 s instead of ~13 s.
Upstream note: FreeBSD sys/dev/usb/serial/uipaq.c carries the identical
loop (inherited from OpenBSD/NetBSD) β coordinate the fix upstream.
DF-5359 VERDICT β untested (rig cannot present the precondition; Low severity per-contract skip)
Bottom line
The defect is code-certain by arithmetic: uipaq_attach() (uipaq.c:1129-1136)
retries a control request 64 times with a 100 ms request timeout and an
unconditional ~100 ms inter-iteration pause, with no early-out for
"device rejected" or "device gone". The loop executes on the per-bus USB
explore thread inside usb_probe_and_attach() (usb_device.c:1286-1345, via
uhub_explore_sub()), while the device enum lock is held (usb_device.c:1324),
so a crafted device matching any of the ~300 VID:PID pairs in uipaq_devs[]
stalls hub-event processing for that bus for ~6.4β12.8 s per insertion,
repeatably via replug. Bounded (no unbounded loop, no memory-safety impact),
hence Low.
Why untested, honestly
- The project QEMU rig (DF-5320's) attaches a
-device usb-serial= Netchip 0525:7701, which binds uftdi, not uipaq (no 0x0525 entries inuipaq_devs[], uipaq.c:150-1061). - QEMU 7.2 on the host exposes no vendorid/productid override for any
attachable USB device model (
-device usb-net,helpand-device usb-storage,helpwere queried: novendoridproperty), so a uipaq-matching VID:PID cannot be presented at all. - No USB host-passthrough hardware (real iPAQ / Facedacer) is available.
- Severity is Low β per the audit contract, Low/Info findings are skipped
in Phase V; no guest time was spent and the guest was left untouched
(status
untested, attempts 0, guest_dirty 0).
What was runtime-verified (attack-surface existence only, guest was up)
/boot/kernel/uipaq.koexists on the audit guest;kldstatshows it is not loaded until a matching device appears (module auto-load path via uhub/devd), i.e. the attack surface is real on a stock system the moment a crafted device is plugged in.
Fix validation
Not performed (nothing to compare against without the precondition device).
fix.diff is authored from the line-level understanding: retry only while
the error is USB_ERR_TIMEOUT (the genuine "device not ready yet" case),
break immediately on success/stall/gone, and cap iterations at 16 so the
worst case drops from ~12.8 s to ~3.2 s. fix_status = not_tested.
Kernel references
- sys/bus/u4b/serial/uipaq.c:1124-1136 (the loop)
- sys/bus/u4b/serial/uipaq.c:150-1061 (VID:PID match table)
- sys/bus/u4b/usb_device.c:1286-1345 (usb_probe_and_attach caller context)
- sys/bus/u4b/usb_device.c:1324 (enum lock held across attach)
- sys/bus/u4b/usb_hub.c:995-1090 (uhub_explore serialization)
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
not_testableFix validation not applicable: no baseline reproduction was possible on the rig (no QEMU device model can present a uipaq-table VID:PID), so there is no observable to compare against. fix.diff is authored from the verified line-level root cause; the retry-only-on-timeout semantics preserve the WinCE warm-up behavior while bounding the stall.
fix.diff (git-apply-able, never applied to sys/)
Confirmed kernel references
Detail
Exploit chain
Attacker with physical USB access plugs a scripted device (e.g. Facedancer/gadget board) presenting a uipaq_devs[] VID:PID (e.g. 0x049f:0x0032, uipaq.c:300) whose EP0 NAKs or STALLs the class-interface request; uipaq.ko auto-loads and uipaq_attach burns 6.4-12.8s on the bus's USB explore thread (replug to repeat), delaying enumeration/detach of all other USB devices on that bus. No escalation path - availability only.
Evidence (decisive lines)
README.md (bug arithmetic + manual Facedancer reproduction steps); VERDICT.md (why untested: no QEMU device model can present a uipaq VID:PID; usb-serial binds uftdi); fix.diff (retry-only-on-timeout + 16-iteration cap). Guest-side surface check: /boot/kernel/uipaq.ko exists, not loaded by default.
PoC changes
None - no seed PoC existed and none was buildable: the finding requires a physical/scriptable crafted USB device that NAKs or STALLs EP0 control requests, which no QEMU 7.2 device model can emulate (verified -device usb-net,help / usb-storage,help have no vendorid override; usb-serial's fixed 0525:7701 binds uftdi).
Verified recommended fix
Retry the magic request only while it times out (error == USB_ERR_TIMEOUT), break immediately on success/stall/device-gone, and cap iterations at 16 (~3s worst case instead of ~13s) - see fix.diff.
Verdict
Code-certain bounded DoS, untested on the rig because the precondition (a USB device matching uipaq's VID:PID table with an unresponsive control endpoint) cannot be presented by any QEMU 7.2 device model: -device usb-serial is Netchip 0525:7701 which binds uftdi, and no vendorid/productid override property exists for usb-net/usb-storage (verified via '-device
No comments yet.