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

Root-hub usbconfig reset never returns: unbounded uninterruptible wait on the root hub's never-serviced re_enumerate_wait β€” kill -9-immune D4 process and whole-USB-bus wedge until reboot (u4b core, reproduced through the uftdi rig; the D4 hang the umodem pass-2 observed)

Summary

usbconfig reset on the root hub with any child attached puts the resetting process in D4/USBSLP immune to kill -9 and kills all USB I/O for the bus until reboot: the generic-usb ioctl tail spins uninterruptibly on re_enumerate_wait but the only code that clears it runs solely for hub children - the root hub's parent_hub==NULL so its flag is set and never serviced (usb_hub.c even contains the dead 'cannot reset root HUB' branch). Reproduced 3x; open port not required. Root-triggered, system-wide consequence. Bound-the-wait + service-root-hub fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5423 Β· 7 files
FileTypeDescriptionSize
README.md β€” 3.3 KB ↓ raw
VERDICT.md β€” 2.9 KB ↓ raw
env.txt β€” 304 B view raw
fix.diff β€” 758 B view raw
run.log β€” 358 B view raw
run.sh β€” 104 B view raw
verdict.json β€” 3.5 KB view raw

DF-5423 β€” root-hub usbconfig reset never returns: unkillable D4 process + permanent USB-subsystem wedge (u4b core, reproduced through the uftdi rig)

What (human-readable)

usbconfig -d ugen0.1 reset (reset of a root hub) with any device attached to that root hub:

  • the usbconfig process enters state D4, wchan USBSLP and never returns β€” kill -9 cannot remove it;
  • subsequent USB operations wedge too (usbconfig list hangs β€” the bus explore path is dead);
  • the guest otherwise stays alive; only a reboot clears it.

Reproduced 3Γ— (twice with an open/writing uftdi port, once with the port closed and the device merely attached β€” the open tty is not required). This matches the D4 hang the umodem.c pass-2 observed while probing this family; it is a u4b-core defect (usb_dev.c + usb_hub.c), with uftdi as the attached passenger device.

Root cause (path:line)

c /* Wait for re-enumeration, if any */ while (f->udev->re_enumerate_wait != USB_RE_ENUM_DONE) { usb_unref_device(cpd, &refs); usb_pause_mtx(NULL, hz / 128); /* "USBSLP", uninterruptible */ if (usb_ref_device(cpd, &refs, 1)) { err = ENXIO; goto done; } }

β€” an unbounded, uninterruptible spin-wait.

  • For the root hub the flag is set by the reset ioctl (usb_generic.c:1801-1802, re_enumerate_wait = USB_RE_ENUM_START), but uhub_explore_handle_re_enumerate() β€” the only code that clears it (usb_hub.c:474,498,511,515) β€” is invoked only for hub children (usb_hub.c:550, inside uhub_explore's per-port loop). The root hub has parent_hub == NULL; nothing ever services its re_enumerate_wait, so the wait loop spins forever, one USBSLP sleep at a time. (usb_hub.c:461-467 already special-cases "the root HUB cannot be re-enumerated" β€” but that code is unreachable for the root hub itself.)

  • The observed child detach/re-attach (dmesg: ugen0.2 (disconnected) β†’ device still open, forcing close β†’ detached β†’ re-attach) comes from the root-hub power-mode change that the same ioctl triggers β€” cosmetic w.r.t. the hang.

Who can trigger

Root only (usbconfig reset needs the 0600 root ugen node). The wedge is system-wide and permanent though: one command kills the whole USB bus (hotplug, enumeration, new opens of usbconfig) until reboot, and leaves an unkillable D4 process.

Reproduce

usbconfig -d ugen0.1 reset        # any device attached to the root hub
ps -ax -o pid,stat,wchan,command | grep usbconfig
#   -> D4  USBSLP  usbconfig -d ugen0.1 reset
kill -9 <pid>                     # survives
usbconfig list                    # hangs

run.log is the clean 3rd reproduction (device attached, port NOT open): D4/USBSLP, kill -9 "Killed" yet the process still present, and usbconfig list hanging (the ssh session had to be cut by the operator).

Fix

fix.diff (usb_dev.c): bound the re-enumerate wait (~2 s of hz/128 sleeps β†’ ETIMEDOUT), with a comment pointing at the unserviced root-hub flag. git apply --check passes against sys/. (A fuller fix would service the root hub's own re_enumerate_wait from the bus explore thread; the bound removes the unkillable-wedge failure mode either way.)

VERDICT.md
↓ download raw

DF-5423 VERDICT β€” root-hub reset unkillable D4 wedge

Status: reproduced (3Γ—; live on the guest: D4/USBSLP, SIGKILL-immune, USB subsystem wedged until reboot). Impact: dos (root-triggered; system- wide USB outage + unkillable process, reboot required). Confidence: certain.

Reproductions

  1. (17:2x, with a 1 MiB write in flight on /dev/cuaU0): usbconfig -d ugen0.1 reset inside timeout 12 β€” the timeout's SIGTERM could not kill it; ps: 964 D1 USBSLP; kill -9 964 β€” process still present minutes later; later usbconfig list produced no output (hang).
  2. (T4a, fresh boot, port closed, device merely attached): same command β€” 1075 D4 USBSLP; kill -9 reported "Killed" but ps still showed 1075 in D4; timeout 10 usbconfig list never produced output; guest ssh/uptime fine. β†’ the open tty is NOT a precondition; any child device on the root hub suffices. This run is run.log.
  3. (first observation, 17:1x): identical with the writer hung.

This is the same D4 hang the umodem.c pass-2 run observed while probing this family; it is not driver-specific (the uftdi port was closed in reproduction 2).

Root cause (path:line) β€” see README.md

  • Unbounded uninterruptible wait: sys/bus/u4b/usb_dev.c:1139-1149 (while (f->udev->re_enumerate_wait != USB_RE_ENUM_DONE) usb_pause_mtx (NULL, hz/128) β€” wchan USBSLP, usb_util.c:134).
  • The waited flag is set for the root hub by the reset path (usb_generic.c:1801-1802) but only hub children are ever serviced (sole clear sites usb_hub.c:474/498/511/515, reached only from the per-port call at usb_hub.c:550). Root hub has parent_hub == NULL (usb_hub.c:461-467's own "cannot reset root HUB" branch proves the authors knew, yet the flag is still left pending) β†’ the wait never ends.

Impact ceiling

Root-only trigger (ugen node 0600), but the failure is a permanent, reboot-required, whole-bus outage plus a SIGKILL-immune process β€” a foot-gun for any admin script that touches root-hub reset (and the same wait loop is reachable for non-root devices where the explore thread CAN service the flag, so the unbounded loop remains a latent hazard for wedged explore threads, e.g. when combined with DF-5422's close hang holding the explore path).

Fix validation

fix.diff bounds the loop (256 Γ— hz/128 β‰ˆ 2 s β†’ ETIMEDOUT). git apply --check passes; not boot-tested (full kernel rebuild out of scope for this pack) β€” fix_status not_testable, mechanism trivial by inspection (adding a loop bound cannot leave the wait longer).

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_testable

fix.diff authored against read-only sys/ (usb_dev.c), git apply --check passes. Not boot-tested (full kernel rebuild out of scope for this pack); adding a loop bound cannot lengthen the wait - mechanism trivial by inspection.

fix.diff; run.log baseline (D4/USBSLP, kill -9 immune, usbconfig list wedge)
↓ fix.diffper-fix-DF-5423

Confirmed kernel references

Detail

Exploit chain

root runs usbconfig -d ugen0.1 reset (ugen node 0600) with any device on the root hub -> unkillable D4 process + permanent whole-bus USB outage (hotplug/enumeration/userland usb ioctls) until reboot. No memory-safety primitive.

Evidence (decisive lines)

run.log: '1075 D4 USBSLP usbconfig -d ugen0.1 reset', kill -9 reported Killed yet the ps line persists, and 'timeout 10 usbconfig list' produced no output (wedged); two further reproductions earlier in the session (964 D1 USBSLP surviving kill -9; T4 with 1MiB writer in flight). dmesg during the cycles: ugen0.2 disconnected -> 'device still open, forcing close' -> detached -> re-attach (root-hub power-mode churn), guest otherwise alive.

PoC changes

No seed PoC existed; trigger is a one-line admin command plus ps/kill probes (run.sh).

Verified recommended fix

Bound the re-enumeration wait loop in usb_dev.c:1139-1149 (~2 s of hz/128 sleeps -> ETIMEDOUT) and/or service the root hub's own re_enumerate_wait from the bus explore thread (full diff in fix.diff).

Verdict

Root-hub reset never returns and wedges the USB subsystem until reboot, reproduced 3x through the uftdi rig (twice with an open writing tty, once with the port closed and the device merely attached - the open tty is NOT required). usbconfig -d ugen0.1 reset puts the process in D4 on USBSLP; kill -9 cannot remove it; subsequent usbconfig list hangs (bus explore path dead); only a reboot clears it. Root cause: the generic-usb ioctl tail at usb_dev.c:1139-1149 is an unbounded uninterruptible spin-wait on re_enumerate_wait, and the root hub's flag is never serviced because uhub_explore_handle_re_enumerate() (the only clear sites, usb_hub.c:474/498/511/515) is invoked only for hub children (usb_hub.c:550); the root hub has parent_hub == NULL. Matches the D4 hang the umodem.c pass-2 observed on this family.