# DF-5301 VERDICT — uhub_child_location_string/pnpinfo_string TOCTOU

**Status: not_reproduced** (code defect certain; exploitable interleaving
not closed on this kernel after an extensive, instrumented campaign).

## The defect (source-certain)

`uhub_child_location_string()` (sys/bus/u4b/usb_hub.c:1660) and
`uhub_child_pnpinfo_string()` (:1702) do:

```c
if (!device_is_attached(parent)) { ... return; }   /* usb_hub.c:1668 */
sc  = device_get_softc(parent);                    /* :1674 */
hub = sc->sc_udev->hub;                            /* :1675 */
uhub_find_iface_index(hub, child, &res);           /* :1677 -> :1637 nports = hub->nports */
```

`device_is_attached()` is `dev->state >= DS_ATTACHED`
(sys/kern/subr_bus.c:1902-1905).  In DFly's `device_detach()`
(sys/kern/subr_bus.c:2129-2153) the state stays `DS_ATTACHED` for the
whole duration of `DEVICE_DETACH()` and until after `device_printf()`,
`devremoved()`, `devclass_delete_device()` — i.e. the guard returns
TRUE during the *entire* body of `uhub_detach()` and for a tail of
~100-300 µs after it.  But `uhub_detach()`:

```c
kfree(hub, M_USBDEV);          /* usb_hub.c:1590 — hub freed */
sc->sc_udev->hub = NULL;       /* usb_hub.c:1592 — hub NULLed */
```

inside that window.  A reader between the check and the deref gets
either a freed `struct usb_hub *` (UAF read; `hub->ports`,
`hub->hubudev` at :1639-1640) or NULL (kernel NULL-deref panic at
:1637).  Additionally `usb_bus_port_get_device()`
(sys/bus/u4b/usb_hub.c:2190-2201) reads `bus->devices[up->device_index]`
without `usb_ref_lock` and without a reference, so the port walk inside
`uhub_find_iface_index()` can chase a `usb_device` that
`usb_free_device()` (sys/bus/u4b/usb_device.c:2138-2224) has cleared
and kfree'd, feeding freed-heap-derived values into the user-readable
location/pnpinfo strings.

Entry points (verified live on the guest):

* `dev.uhid.0.%location` → `bus=0 hubaddr=2 port=1 devaddr=3 interface=0 ugen=ugen0.3`
  — served by uhub1's `uhub_child_location_string` — world-readable.
* `hw.bus.devices` global walk (sys/kern/subr_bus.c:3873-3905) calls
  both methods for every device — world-readable.

## Reproduction attempt (extensive)

Environment: DragonFly 6.5-DEVELOPMENT #0 X86_64_GENERIC (Jul 2 2026),
6 vCPU KVM, QEMU `-device piix4-usb-uhci,id=uhci0` with
`usb-hub` on root port 1 (`uhub1`, 8 ports) and two `usb-tablet`
children on ports 1.1/1.2 (`uhid0`, `uhid1` — children of uhub1).

* Reader storm as *unprivileged* user (`maxx`, no groups):
  60 threads tight-looping the four vulnerable sysctls plus a global
  walk thread — sustained **~155,000-190,000 successful dispatches/s**
  while the hub exists (hammer-tail.log).
* Host-side replug loop (QEMU monitor): `device_del h1` /
  re-`device_add` of hub+tablets — **953 full `uhub_detach` windows**
  generated (detach_count.txt), each producing the
  `kfree(hub)`/`hub=NULL` transition with the device still
  `DS_ATTACHED`.
* Four campaigns (replug3/replug4 logs): 400 + 300 cycles at full load,
  plus ~350 earlier cycles with a diluted hammer.

**Result: no panic, no fault, guest stayed up through every window.**

## Why the interleaving does not close (traced)

1. **OID lifecycle kills fresh lookups at the window.** The children
   (`uhid0`/`uhid1`) are torn down in `uhub_detach()`'s port loop
   (:1567-1579) *before* `kfree(hub)`.  Their sysctl OIDs are removed
   (device_sysctl_fini inside device_detach of each child) ~1-2 ms
   before `hub=NULL`.  At the vulnerable instant (after :1592) fresh
   sysctl lookups return ENOENT — dispatch rate to the vulnerable
   method is zero exactly when `hub` is NULL.
2. **In-flight readers cannot be stalled in the check→deref interval.**
   The interval is ~10-50 ns of straight-line code with no blocking
   call and no lock.  DFly LWKT does not preempt a kernel-resident
   thread for an equal-priority (user) thread; only an interrupt can
   delay it, and the delay window (~ns-µs) must contain the single
   `hub=NULL` store executing on another CPU — measured/estimated rate
   ≈1e-7/s.  (The `oid_running` drain in `sysctl_remove_oid_locked`
   that *would* have serialized this cleanly — kern_sysctl.c:387-397 —
   is dead code in this tree: the counter is never incremented.)
3. **The global walk re-derives pointers after every sleep.**
   `sysctl_devices` walks the unlocked `bus_data_devices` TAILQ and
   calls the methods per device, but its only blocking points
   (SYSCTL_OUT copyouts) lie *between* devices; after each copyout the
   iterator reloads `TAILQ_NEXT` and `devremoved()` has already
   unlinked the detaching children, so post-stall iterations skip them.

The residual reachable window is thus interrupt/SMP alignment of a
single store inside a ~10 ns instruction gap — not demonstrable.
(Under a kernel change that inserts *any* blocking point between
`device_is_attached()` and the `hub` load — or a reader that holds the
values longer — the defect becomes a NULL-deref panic/UAF-read.)

## Fix

`fix.diff` (applies clean against sys/bus/u4b/usb_hub.c): NULL-guard
`hub` after the `device_is_attached()` check in both methods.  This
converts any future interleaving into a benign empty string.  Fix
validation (patched-kernel build + re-run) was **not** performed: the
baseline race never reproduced, so there is no observable to diff
against; the guard is a trivially-correct hardening (fix_status:
not_testable).

## Bottom line

Real missing-synchronization defect (CWE-367 + CWE-476/CWE-416
pattern), certain in source, reachable only through sysctl reader paths
that are world-readable, but the exploitable interleaving window
collapses to ~nothing on this kernel because of the sysctl OID
lifecycle and DFly's non-preemptible straight-line kernel intervals.
Filed as hardening with a one-line-per-callsite fix.  No impact
demonstrated; not a practical DoS on this system as built.

## 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.
