# VERDICT — DF-5353 (usb_template.c pass 2)

**Bottom line:** `sys/bus/u4b/template/usb_template.c` is **memory-safe against
the malicious-host threat model** — a 187-million-request KLD sweep of the real
descriptor demux (all descriptor types × indices × langids × request types ×
9 templates × 3 speeds) produced **zero anomalies, zero panics**, and the
two-pass descriptor builder proved byte-exact (independently recomputed sizing
== sum of served wTotalLengths). One **Info-level functional defect** was
reproduced: the `USB_TEMP_PHONE` gadget template is compiled into
`usb_template.ko` and advertised by the `usb_ioctl.h` enum, but
`usb_temp_setup_by_index()` has no case for it, so template selection 7 always
fails. One additional **Info hardening note** (scratch-union aliasing) is filed
without a pack.

## What was verified on the guest and how

KLD harness (`usb_template_fuzz.c`, this directory) driving the *shipped*
`usb_template.ko` against a synthetic `struct usb_device`:

| Claim | Result | Evidence |
|---|---|---|
| Dispatch indices bounded (config index vs `bNumConfigurations`, string id vs sibling tables, langid) | Holds — 0 anomalies in 187,272,018 crafted requests | `run.log`, `run.2.log` `anom=0` |
| Served lengths consistent with served buffers (`len==buf[0]`, config walk == wTotalLength, device 18B, qualifier 10B, string ≤255B even) | Holds | `check_response()`/`walk_config()` in harness; 0 anomalies |
| Two-pass build (size pass == fill pass) | Holds — sum(wTotalLength)+sizeof(usb_temp_data) == independent recomputation for every template/speed | `sweep_template()` size check; no `SIZE MISMATCH` lines |
| Invalid template indices rejected | Holds for 9..1023, 0x8000, 0xFF00, 0xFFFF (`USB_ERR_INVAL`, no state left behind) | run logs |
| **`USB_TEMP_PHONE` (7) unreachable** | **Reproduced** — `setup_by_index(7)` → `USB_ERR_INVAL` (err=3), while direct `usb_temp_setup(udev, &usb_template_phone)` succeeds at FULL/HIGH and serves 42 valid descriptors (run.2.log `phone` lines, anom=0) | `run.initial-expectation-fixes.log` line 2; `run.2.log` phone sweep lines |

Guest: `DragonFly dfbsd 6.5-DEVELOPMENT #0 ... x86_64`, usb compiled into
kernel, `usb_template.ko` from `/boot/kernel`. Three full sweep runs
(initial + 2 confirmation), no panic, guest still up; modules unloaded and
`/tmp/utfz` removed afterwards (guest returned to `kldstat` baseline).

## Why the phone finding is only Info

`usb_template_phone` is dead code: it cannot be selected
(`usb_temp_setup_by_index` switch, `sys/bus/u4b/template/usb_template.c:1346-1373`,
has cases for MSC/CDCE/MTP/MODEM/AUDIO/KBD/MOUSE/SERIALNET but **not**
PHONE), so an operator setting `hw.usb.usb_template=7` gets a silent failure
("maybe the USB template module has not been loaded", `usb_device.c:1729`).
No memory-safety, information-disclosure, or privilege impact: the error path
is a clean `USB_ERR_INVAL`. Fix is one line (add the case) or delete the
template (what upstream FreeBSD eventually did).

## Negative results with weight (what was hunted and killed)

1. **OOB via host-controlled `wValue[0]` config index** —
   `usb_temp_get_config_desc()` bounds the index against the generated
   `bNumConfigurations` (`usb_template.c:1046-1049`) and walks only
   kernel-written `wTotalLength` fields; empirically exercised for every index
   0..254 × every request type (0 anomalies).
2. **String/langid dispatch** — `usb_temp_get_string_desc()`
   (`usb_template.c:1095-1109`) forwards host `wIndex`/`wValue[0]` to the
   per-template getters; all nine getters bounds-check
   `string_index < *_MAX` against designated arrays and gate langid == 0x0409
   (e.g. `usb_template_msc.c:170-193`); MTP's 0xEE/MS-OS paths return fixed
   static descriptors. Full index/langid sweep clean.
3. **wTotalLength math / two-pass builder** — sizing pass and fill pass walk
   identical const template tables; `wTotalLength` is written as the exact
   chunk size (`usb_template.c:398-403`); proven equal to an independent
   recomputation for all 27 template/speed combos.
4. **Registry/unload races** — the publish-then-maybe-free window
   (`usb_template_ptr` set at `usb_template.c:1288`, freed at `:1322` on
   resolve failure) is unreachable by concurrent control requests: gadget ep0
   transfers are only started by `uhub_explore` → `usbd_ctrl_transfer_setup`
   (`usb_hub.c:563-565`), i.e. after `usb_alloc_device()` (which contains the
   only `usb_temp_setup_by_index_p` call site, `usb_device.c:1727`) completes
   and registers the device. `usb_temp_unload()`'s 1-second `tsleep` grace
   (`usb_dynamic.c:117-130`) is a `kldunload`-time (root-only) hazard, not an
   attacker path.
5. **Fragmented control transfers** — the consumer's `max_len -= off`
   (`usb_handle_request.c:746`) cannot underflow via template descriptors:
   `control_rem` is decremented by bytes actually sent
   (`usb_transfer.c:1608-1626`), and every fragment serves the same
   deterministic descriptor, so cumulative `off` ≤ served length. The
   subdev-handlers that *can* change demux results mid-transfer use the
   `offset==0 ? payload : 0` pattern (e.g. `gadget/g_modem.c`), keeping
   `max_len - off == 0` on later fragments.
6. **`usb_hw_ep_get_needs` bounds** — `ep_curr` bounded by
   `ep_end = ues->ep + USB_EP_MAX` (`usb_template.c:686,726-729`);
   `bmInAlloc/bmOutAlloc` are 2 bytes and every index derives from
   `UE_ADDR(0x0f)` or `best_n < USB_EP_MAX/2` (`:586,642-651,760-786`).
7. **Kernel-side writes from host data** — none: `usb_temp_get_desc()` serves
   only READ requests; all SET requests stall to the vendor fallback
   (`usb_template.c:1140-1216`); MTP's vendor descriptor is gated on
   `UT_READ_VENDOR_DEVICE` + fixed wIndex values returning static data.

## Known-fragile-but-benign (filed as separate Info hardening note)

`union usb_device_scratch` (`usb_device.h:171-179`) overlays
`struct usb_temp_setup` (32 B) on `usb_hw_ep_scratch_sub ep[0..1]`;
`usb_hw_ep_resolve()` memsets and repopulates the union
(`usb_template.c:915-922`) *inside* `usb_temp_setup()`. Today this is safe
only because `uts->err` is re-stored from the resolve return value
(`:1298,1311`) before every read and `uts->buf/size` are never read after
resolve begins — the harness's clean PASS runs (setup err==0 for all
FULL/HIGH template builds) confirm the current ordering holds.

## Cosmetic noise (no finding)

Every template-setup failure path calls `usb_temp_unsetup()` with
`usb_template_ptr == NULL` → `usbd_free_config_desc: nullpointer` console
spew (`usb_template.c:1322,1334-1339`; `usb_request.c:1318-1326`). Observed
33× during the runs. Log noise only.

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