# DF-5555 — VERDICT

**Finding:** `usb_make_raw_desc()`'s CDC renumbering heuristic
misclassifies the audio template's USB-Audio CS_INTERFACE descriptors;
two AS_GENERAL `bDelay` bytes are corrupted in the generated config
descriptor (iface 1: `0x03→0x04`, iface 2: `0x01→0x03`), and seven more
descriptors in interface 0 sit on the predicate with zero delta today.

**Verdict: reproduced** (deterministic structural replay against the
guest's own sha-verified source; same evidence class as DF-5553).
**Impact: none** for privilege/memory safety — see "Impact ceiling".

## 1. Root cause (path:line)

- The core patcher discriminates CDC descriptors ONLY by generic shape:
  `sys/bus/u4b/template/usb_template.c:135-137` (union: `raw[0] >= 5 &&
  raw[1] == UDESC_CS_INTERFACE(0x24) && raw[2] == UDESCSUB_CDC_UNION(6)`
  → bytes 3,4 `+= temp->bInterfaceNumber`) and
  `usb_template.c:162-164` (call management: same with subtype
  `UDESCSUB_CDC_CM(1)` → byte 4 `+= bInterfaceNumber`). Struct layouts:
  `sys/bus/u4b/usb_cdc.h` (`usb_cdc_union_descriptor` 5 bytes,
  `usb_cdc_cm_descriptor` 5 bytes, both `__packed`).
- USB Audio 1.0 class-specific descriptors ALSO use type 0x24 with
  subtypes that collide: AS_GENERAL = 0x01, FEATURE_UNIT = 0x06
  (`sys/bus/u4b/template/usb_template_audio.c:205-208,273-276,141-169`).
- The interface numbering walk (`usb_template.c:307-314` incrementing
  `bInterfaceNumber` seeded to 0xFF at `usb_template.c:379`) assigns:
  iface 0 = `audio_iface_0` (AC, usb_template_audio.c:196), iface 1 =
  `audio_iface_1_alt_0/1` (AS playback, :254/:263), iface 2 =
  `audio_iface_2_alt_0/1` (AS record, :312/:321). Raw descs are scanned
  at `usb_template.c:318-325` with the CURRENT interface number, endpoint
  raws at `usb_template.c:209-215`/`:332-338`.

## 2. Census (checklist item b) — what the harness proved

9 of the 22 raw tables satisfy the predicate:

| table | line | subtype | real meaning | iface | patched bytes | delta |
|---|---|---|---|---|---|---|
| `audio_raw_desc_0` | :96 | 1 | AC header | 0 | [4] = `bcdADC` hi | +0 |
| `audio_raw_desc_9` | :141 | 6 | feature unit | 0 | [3]/[4] = `bUnitID`/`bSourceID` | +0 |
| `audio_raw_desc_10` | :146 | 6 | feature unit | 0 | same | +0 |
| `audio_raw_desc_11` | :151 | 6 | feature unit | 0 | same | +0 |
| `audio_raw_desc_12` | :156 | 6 | feature unit | 0 | same | +0 |
| `audio_raw_desc_13` | :161 | 6 | feature unit | 0 | same | +0 |
| `audio_raw_desc_14` | :166 | 6 | feature unit | 0 | same | +0 |
| `audio_raw_desc_20` | :205 | 1 | **AS_GENERAL** | **1** | **[4] = `bDelay` 0x03→0x04** | **+1** |
| `audio_raw_desc_30` | :273 | 1 | **AS_GENERAL** | **2** | **[4] = `bDelay` 0x01→0x03** | **+2** |

The CS_ENDPOINT descriptors (`audio_raw_desc_22` :216,
`audio_raw_desc_32` :284, type 0x25) and all type-II/format descriptors
(subtype 2) never match — verified by scanning endpoint `ppRawDesc`
tables with the same interface number, exactly as the core does
(`usb_template.c:209-215`).

All writes land at offset 4 (or 3+4) with `bLength >= 5` checked first,
inside the heap config buffer the core just `memcpy`'d the table into —
**no OOB write is possible from this bug**; the const source tables are
never written. This is deterministic descriptor-semantics corruption,
not memory corruption.

The seven interface-0 hits are the family's "rebase hazard": the bytes
the heuristic would patch are `bUnitID`/`bSourceID` (feature units) and
`bcdADC`-hi (AC header). In-tree they get +0 because the AudioControl
interface is numbered 0, but any future nonzero-interface embedding of
these tables (composite gadget, reordered config) silently corrupts the
audio graph identity fields instead of a harmless hint byte.

## 3. Why "reproduced / impact none"

- The generated-vs-authored byte diff in `run.log` shows exactly two
  corrupted bytes, deterministic across runs (`run.log` ==
  `run.2.log`).
- Activation of this template is privileged: `USB_SET_TEMPLATE` requires
  `caps_priv_check_self(SYSCAP_NODRIVER)` (`sys/bus/u4b/usb_dev.c:1716-1721`);
  the global `usb_template` default is set by the `hw.usb.usb_template`
  boot tunable / `hw.usb.template` sysctl (root;
  `sys/bus/u4b/usb_device.c:112-119`; guest default observed: 0 = MSC),
  and generation only runs on a gadget-side device attach
  (`usb_device.c:1727` → `usb_temp_setup_by_index`,
  `usb_template.c:1359-1361` — AUDIO, unlike PHONE, does have a case).
  The guest has no device-side USB controller, so no runtime EP0 path
  exists to observe; the structural replay against sha-identical source
  (`dd3d65e1…`, guest `/usr/src` == audit tree) is the honest maximum,
  per the family standard set by DF-5553.
- Corruption ceiling: a malformed `bDelay` hint (and under rebase,
  `bUnitID`/`bSourceID`/`bcdADC`) in descriptors presented to a USB
  HOST by a root-configured gadget. No unprivileged trigger, no kernel
  memory effect, no host-side parser in this tree consumes the bytes
  (the corruption is visible to whatever host the gadget is plugged
  into). Hence Info severity, impact=none — identical classification to
  DF-5553.

## 4. Everything else the family checklist hunts — verified negative

Proven by the same binary (`run.log`, all PASS):

- **(a) Descriptor tables:** S1 — `bLength == sizeof(table)` for all 22
  raw tables (the `memcpy` at `usb_template.c:131` cannot over-read);
  S2 — all 9 pointer tables (`audio_raw_iface_0/1/2_desc`,
  `audio_raw_ep_1/2_desc`, `audio_iface_1/2_ep`, `audio_interfaces`,
  `audio_configs`) are NULL-terminated with no interior hole.
- **(c) String tables:** S3 — all five string descriptors self-sized and
  even; S4 — full 0..255 index sweep of `audio_get_string_desc()`
  (usb_template_audio.c:375-396): non-NULL exactly for index 0 (any
  lang) and 1..4 @ 0x0409, NULL otherwise — bounded by
  `string_index < INDEX_AUDIO_MAX` (:392), no OOB, designated
  initializers cover all 5 slots (:378-384), iInterface/iConfiguration
  indexes all in-range.
- **(d) Vendor/HID + iface hardcodes:** S5 — `usb_template_audio` sets
  no `getVendorDesc` (:354-366); the core short-circuits to NULL
  (`usb_template.c:1050-1060`), so there is no vendor/HID length
  contract to violate. S6 — interface numbers hardcoded inside raw
  descriptors match the numbering walk: AC header `bInCollection`=2 ==
  AS interface count, `baInterfaceNr`={1,2} == walk output
  (:96-99, confirmed by S6 checks), and `wTotalLength` 0x00a9 == exact
  sum (169) of the 16 AC-interface descriptor lengths. S8 — every
  `bSourceID`/mixer/selector pin in the AC interface resolves to a
  defined unit/terminal ID.
- **(e) Speed tables:** S7 — `audio_isoc_mps.mps[]`/
  `audio_isoc_interval.bInterval[]` indexed only with
  USB_SPEED_FULL/USB_SPEED_HIGH (< USB_SPEED_MAX, in-bounds;
  usb_template_audio.c:231-239); other speeds read 0 → the core fails
  closed with `USB_ERR_INVAL` (`usb_template.c:221-229`).
- No dynamic state, locks, refcounts, or copyin/copyout exist in this
  file (const tables + one pure callback) — the race/refcount/leak
  classes are structurally N/A; the EP0 length clamp
  (`usb_handle_request.c:744-748`, cited in DF-5553) covers the
  readback side for the whole family.

## 5. Fix and validation

`fix.diff` is the family fix authored in DF-5553 and re-validated here
at table level: thread the containing interface's `bInterfaceClass` into
`usb_make_raw_desc()` and gate the CDC union/CM renumbering on
`UICLASS_CDC`/`UICLASS_CDC_DATA` interfaces. The `-DFIX_CLASSGATE`
binary (identical harness with the gate compiled in) drops the misfires
from 9 (two with nonzero `bDelay` deltas) to **0** while all S1-S8
checks still pass — with the gate, the audio template's generated
descriptors equal the authored bytes by construction.

A kernel-level A/B build is `not_testable` for the same reason as
DF-5553: observing the generated bytes at runtime requires a
device-side USB controller that this guest (and no in-tree default
config) provides, plus root to select the template; there is no
observable kernel behavior to compare. The logic-level validation is
complete and deterministic.

## 6. Artifacts

- `table_check.c` — harness (verbatim body include + predicate/numbering
  replay + S1-S8). sha256 `4d7cf91d169e6b5fd2b2bbc04538dfaa94fb88eac4841f882f00a73f2b78680e`
- `audio_body.inc` — 339 lines extracted from the guest's
  `/usr/src/sys/bus/u4b/template/usb_template_audio.c`
  (sha256 of source `dd3d65e1…` == audit tree)
- `build.log`, `run.log`, `run.2.log`, `env.txt` — full untrimmed guest
  output; `run.log == run.2.log`
- `fix.diff` — family class-gate fix (core-side)
- `manifest.json`, `verdict.json`

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