# DF-5426 VERDICT — `sys/bus/u4b/usb_handle_request.c` pass 2

**Mode:** audit (pass 2, GLM 5.3). **File:** 804 LOC, gadget EP0 engine.
**Result:** 1 Info hardening finding (this ID) + a verified negative
result for everything else. Live evidence: production code executed in
a KLD rig on the QEMU guest (DragonFly 6.5-DEVELOPMENT #0 Thu Jul 2
06:02:54 UTC 2026), 41/41 containment expectations met twice, zero
guard-zone hits, zero panics, module load/unload clean.

## What was audited and how the candidates died

The engine demultiplexes host-issued EP0 setups (`req` is 8 bytes of
fully host-controlled data re-read from `frbuffers[0]` each callback,
`usb_handle_request.c:512`), computes a per-fragment data length, and
either copies into the ep0 DMA window (`usbd_copy_in`, `:781`) or aims
the hardware at a provider buffer zero-copy (`usbd_xfer_set_frame_data`,
`:785`). Fragment offsets derive from `off = wLength - control_rem`
(`:514-522`) where `control_rem` is managed by
`usbd_setup_ctrl_transfer` (usb_transfer.c:1507-1654: seeded once from
`wLength` at `:1548/:1464`, monotone, `len <= control_rem` enforced at
`:1608`, `force_short_xfer -> control_rem = 0` at `:1616-1617`).

Candidate kill list (all traced at `path:line`, then live-checked):

1. **`:746 max_len -= off` underflow (the ustorage pass-2 question).**
   Three separate proofs it cannot fire in-tree:
   * *Iface/driver path:* the dispatcher does `*plen += off`
     (`usb_handle_request.c:285`) before returning, so
     `max_len = (plen + off) - off ≡ plen (mod 2^16)` — **exact
     cancellation**, for any driver, however lying. Rig: D01-D04
     (conformant) and P2 (lying 1024→300) confirm `max_len == plen`.
   * *Internal paths* (`GET_CONFIG` len 1 `:654`, `GET_STATUS` len 2
     `:672`, `GET_EP_STATUS` len 2 `:736`): `off` advances only by
     bytes previously delivered, each `≤ max_len = len - off`; hence
     `off ≤ len` by induction. Rig I01-I09.
   * *Template path* (`:644`, full-length contract): same induction,
     providers are pure functions of the immutable re-read setup.
   * *The one feeder that can push `off` past the provider length —
     `force_short_xfer` zeroing `control_rem` (usb_transfer.c:1616) —
     forces `rem == 0`, and the `if (max_len > rem)` clamp (`:753`)
     zeroes `max_len`. Rig I02/I06/T02/T03/P1c.
2. **`:778-792` copy/DMA bounds.** `max_len ≤ min(1024, rem,
   provider_len - off)`; ep0 DMA geometry is 8 (setup) + 1024
   (`USB_EP0_BUFSIZE`, usb_dragonfly.h:84; proxy rounding + REQ_SIZE,
   usb_transfer.c:666-727). `usbd_copy_in` therefore lands inside the
   window (rig mcopy-window check, I01-I09), and zero-copy windows stay
   inside provider objects for all conformant providers (T01-T12, D01-
   D04). `GET_INTERFACE` is served zero-copy from `&iface->alt_index`
   (`:339`) — 1 byte, contained (rig F03).
3. **Uninitialized stack reads.** `temp` union fully initialized on
   every read path (`:652`, `:670`, `:732-734`); completion-state gating
   (`:543`, `:741`) prevents stale reads. Rig E01-E03.
4. **Sub-dev TOCTOU** while the xfer lock is dropped (`:232`): the
   enum lock is held across dispatch (`:235-352`) and detach
   (`usb_unconfigure`, usb_device.c:486-524) takes the same lock. No
   window.
5. **Broadcast iface iteration** (`:295-297`): `usbd_get_iface` is
   bounds-checked (usb_device.c:2237-2244); `iface_index` is `uint8_t`
   and the loop ends at the first NULL/idesc-NULL iface. Rig F06
   (walks 2 valid + stops at idesc==NULL), F04/F05 (wIndex 3/255).
6. **SET_INTERFACE/alt, endpoint halts, set-address, set-config:**
   alt validated against `usbd_get_no_alts` over the kernel-built
   config (`:193`, `:314-329`, rig F01/F02); `usbd_get_ep_by_addr`
   masked and bounded (usb_device.c:177-217); `usbd_set_endpoint_stall`
   NULL-ep-safe (usb_device.c:976-986, rig I13-I15); address `≥ 0x80`
   stalled and 7-bit masked (`:677/:685`, rig I10-I12); `conf_no--`
   guarded by the UNCONFIG branch (`:156-164`) and the descriptor
   lookup bounds the index (rig: SET_CONFIG not runtime-driven — it
   needs a configured device graph; static trace only).
7. **Scratch/template lifecycle:** descriptors live in the
   `udev->usb_template_ptr` heap block / static string tables; freed
   only from `usb_free_device` *after* `usbd_transfer_unsetup` of the
   ctrl xfers (usb_device.c:2183→2186) — no host-reachable UAF window.
   Vendor/string providers are pure functions of `req`
   (usb_template_msc.c:173-193, usb_template_mouse.c:…, modem NULL).
8. **Stale zero-copy frame pointers:** both frame buffers are reset to
   `local_buffer+{0,8}` on every callback (`:507-508`).
9. **Diff vs FreeBSD main:** functionally byte-identical (headers/
   cosmetics only) — no missed upstream fix.

## The finding (DF-5426, Info, hardening)

`max_len -= off` (`:746`) has **no explicit underflow guard**. The
in-tree safety rests on two implicit invariants — the mod-2^16
cancellation (`:285`) and provider-length immutability within one
control transfer. Nothing in the file *enforces* the second. If a
provider's length ever shrinks below the already-transferred offset
(out-of-tree `usb_temp_get_desc_p` override, or a future in-tree
provider that rebuilds descriptors per call), `max_len` wraps; the
`:750/:753` clamps bound it to 1024 (and `rem`) but **not to the
provider object**: the frame window `[src_zcopy + off, +max_len)`
escapes the object by up to `1024 + off - real_len` bytes per
fragment.

Rig proof on the production code (baseline run):
* **P1a** — provider shrinks 4096→100 at `off=1024`, `wLength=4096`:
  fragments 2..4 read `[base+1024, base+2048)`, `[+2048, +3072)`,
  `[+3072, +4096)` from a 100-byte object — escapes of 1948/2972/3996
  bytes (IN direction ⇒ OOB DMA read of adjacent kernel memory to the
  hostile host). `esc_bytes=8916` total.
* **P1b** — shrink 1024→4 at `off=1024`: escapes of 2044/3068/4092
  bytes. `esc_bytes=9204`.
* **P1c** — shrink 200→4 with `wLength=0xFFFF`: the
  `force_short_xfer` termination (`:764-770` + usb_transfer.c:1616)
  ends the transfer after fragment 1 — this shape cannot reach
  fragment 2 (contained).
* **P2** — the same class via a *lying sub-driver* through the real
  kobj `USB_HANDLE_REQUEST` dispatch: `plen` 1024 then 300 at `off>0`
  (cancellation makes `max_len = 300` exactly); window escapes the
  driver's 1024-byte buffer by 300 bytes per fragment; for an OUT
  (host→device) request the same window is the DMA **write** target —
  host-controlled heap overflow past the driver object. Pure
  driver-contract violation; no in-tree driver does this
  (g_audio.c:544-620, umodem.c, ustorage_fs.c, g_* all use the
  `offset==0 ? size : 0` idiom).
* **P3** — driver sets `*plen` but not `*pptr`: NULL frame pointer
  with nonzero length (`:785` `USB_ADD_BYTES(NULL, off)`).

None of P1/P2/P3 is reachable through in-tree code (all in-tree
providers are pure functions of the immutable setup bytes, and the
function-pointer `usb_temp_get_desc_p` is only writable by kernel
code). Hence Info severity / defense-in-depth, not an exploit path.

## Fix and validation

`fix.diff` (authored after verification, never applied to `sys/`):

```diff
-	max_len -= off;
+	if (max_len < off) {
+		/* length wrapped around - avoid underflow */
+		max_len = 0;
+	} else {
+		max_len -= off;
+	}
```

Validated by rebuilding the identical rig against a patched copy of
the production file (md5 `7d86c182…`):

* baseline: `contained=41 failed=0 latent-primitives=4` (P1a/P1b wrap
  escapes present),
* patched: `contained=43 failed=0 latent-primitives=2` — **P1a/P1b now
  `escaped=0`, terminating cleanly with a status stage after the
  honest prefix (`frags=3 sent=1024`)**; all 41 conformant cases
  byte-identical (the guard is a no-op when `max_len >= off`, which
  the induction guarantees for conformant providers).
* P2/P3 persist by design: they are third-party driver-contract
  violations that this layer cannot detect (the kernel does not know
  the provider's real object size); the `:750/:753` clamps remain the
  only bound.

## Rig-harness notes (honesty section)

* The rig models the callback context: xfer lock held across
  `usb_handle_request()` (the `usb_handle_set_stall` /
  `usb_handle_iface_request` unlock/relock paths give it back held).
* `control_rem` lifecycle replicated exactly
  (init-once-from-wLength, `len<=control_rem` assert, `force_short →
  0`); a mismatch would have printed `BOOKKEEPING VIOLATION` (never
  fired).
* Two rig-side iteration artifacts were fixed during bring-up
  (completion-fragment counting, GET_INTERFACE zero-copy
  classification); the production code was never modified for the
  baseline runs.
* One guest panic occurred during harness bring-up and was a RIG bug
  (missing lock discipline), not production behavior; the guest was
  reset (`vm.sh reset with-src`) and all reported runs are from the
  clean snapshot.
* `usb_handle_set_config` / remote-wakeup paths were traced statically
  only (they need a configured device graph / bus explore machinery);
  neither manipulates host-controlled lengths.

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