# VERDICT — DF-5315

**Status: REPRODUCED** (unprivileged local user, fresh boot) —
`reproduced`, impact `dos`: permanent kernel memory leak (~1 MB per
20 s of racing; unbounded if repeated on other endpoints/devices) +
permanent endpoint lockout for every consumer (ugen ioctls,
read()/write(), kernel drivers) until replug/reboot.

## Root cause

`ugen_ioctl()` case `USB_FS_OPEN` checks
`f->fs_xfer[ep_index] != NULL → EBUSY` with **no lock**
(sys/bus/u4b/usb_generic.c:1462) and then calls
`usbd_transfer_setup()`, which (a) sleeps for a long time
(`usbd_enum_lock` usb_transfer.c:942, `kmalloc M_WAITOK` :1220, DMA
setup) and (b) **NULLs its target slot at entry** — `ppxfer[n] = NULL`
(usb_transfer.c:935, no lock) — storing the finished pointer only at
the very end (:1138).  Two barrier-synchronised opens that both pass
the EBUSY check therefore both run a full setup for the same slot; the
second entry's NULL/overwrite orphans the first `usb_xfer`:

* its `endpoint->refcount_alloc` (incremented at usb_transfer.c:1121)
  is never decremented (unsetup does that at :1421) → after
  `USB_EP_REF_MAX` = 0x3F orphans the endpoint rejects every further
  `usbd_transfer_setup()` (:1118-1119);
* its root block (root + xfer + DMA pages, allocated at :1220) is
  never freed → unbounded M_USB growth.

This is the ioctl-side sibling of DF-5287 (which races read()/write()
through `ugen_transfer_setup()` on `f->xfer[]`): different entry, different
array, same orphan mechanism — and the DF-5287/DF-5288 fixes do not
touch `USB_FS_OPEN`, so it survives them (verified: this race was
reproduced on a kernel that already carries the DF-5287+DF-5288 fix
semantics in the tree analysis; the guest baseline is stock #0).

## Reproduction

Guest: DragonFly 6.5-DEVELOPMENT #0, QEMU x86_64, usb-ehci + usb-mouse,
unprivileged `maxx` on `/dev/usb/0.2.0` (0666).

`./df5315 20` — two threads, three barriers per round: both fire
`USB_FS_OPEN` simultaneously; a single serialised closer frees the
survivor.  Result (`run.log`):

* ~60 rounds in ~6 s, essentially **every round double-won** (121
  successful opens, 61 orphans);
* counters freeze at 60+61 (refcount exhausted);
* `vmstat -m` USB: 16 allocs/19.8K → 331 allocs/1.02M (**+61 orphaned
  blocks, ~1 MB leaked in 20 s, never freeable**);
* after full cleanup (`USB_FS_CLOSE` ×4 + `USB_FS_UNINIT` + re-`INIT`)
  a fresh `USB_FS_OPEN` fails **ENOMEM forever**.

## Fix validation

The fix is the `USB_FS_OPEN` setup-claim hunk of `DF-5314/fix.diff`
(self-contained there): the slot is claimed under `priv_lock` with a
sentinel before entering `usbd_transfer_setup()`, the setup runs into a
local pointer, and only the claim winner publishes.  On the patched
kernel (`run.patched.log`): opens serialise (600 successes in 12 s), no
exhaustion, post-cleanup open succeeds, and `vmstat -m` shows zero
orphan growth after the whole suite.

## Kernel references

- sys/bus/u4b/usb_generic.c:1456-1465 (unlocked EBUSY check)
- sys/bus/u4b/usb_generic.c:1538-1540 (setup on the shared slot)
- sys/bus/u4b/usb_transfer.c:935 (entry slot NULL, no lock)
- sys/bus/u4b/usb_transfer.c:942, :1220 (sleeping setup window)
- sys/bus/u4b/usb_transfer.c:1118-1121 (endpoint refcount)
- sys/bus/u4b/usb_transfer.c:1138 (late pointer store)

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

(No fix.diff for this item: documented recommendation only - see Fix above.)
