# DF-2351 — VERDICT: REPRODUCED (panic, deterministic) — FIX VALIDATED

## Bottom line

The claimed truncation and the claimed NULL-deref panic are both real and
were demonstrated **inside the kernel** with the production uvc code paths:

1. `uvc_drv_init_data_fmt()` (production, unmodified, compiled into
   `uvc_rig.ko` from `/usr/src/sys/bus/u4b/uvc/uvc_drv.c`) counted
   **nfmt = 256** (uint16_t local) and stored **data->nfmt = 0**
   (uint8_t field) while allocating the *correctly sized* 24 KB buffer
   (`fmt=0xfffff80118496000`) — the console line proves the truncation
   exactly as the finding describes (uvc_drv.c:1674 vs 1737).
2. `uvc_drv_parse_data()` (production) then completed with
   `data->nfmt == 0`, exactly as at attach time.
3. `uvc_drv_init_cur_fmt_frm()` (production, uvc_drv.c:1457-1463) skipped
   the `for (i = v->data->nfmt; i > 0; i--)` search, left `fmt == NULL`,
   and faulted reading `fmt->nfrm`:

```
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0x8                      <- offsetof(uvc_data_format, nfrm)
Stopped at uvc_drv_init_cur_fmt_frm.cold.36: movq 0x8,%rax
```

   (uvc_drv.c:1463). The guest panicked; ssh died; `vm.sh status` = down.

## Line-by-line trace (all cited in sys/bus/u4b/uvc/)

- uvc_drv.h:662-665 — `uint8_t nfmt, nfrm, nitv` (the truncating store target)
- uvc_drv.c:1674 — `uint16_t nfmt, nfrm, nitv` (counters)
- uvc_drv.c:1690-1698 — `nfmt++` per VS_FORMAT_* descriptor
- uvc_drv.c:1726-1731 — allocation uses the full uint16 values (correct size)
- uvc_drv.c:1737-1739 — `data->nfmt = nfmt` truncation store
- uvc_drv.c:1457-1462 — search loop skipped when nfmt == 0
- uvc_drv.c:1463 — `if (fmt->nfrm == 0)` NULL dereference (panic site)
- same pattern (unfixed) in the GET_CUR fallback at uvc_drv.c:1528-1535

## Production reachability

`uvc_drv_attach` (uvc_drv.c:2673) → `uvc_drv_parse_ctrl` (2140) →
`uvc_drv_parse_data` (1776) → `uvc_drv_init_data_fmt` (1667); then
`uvc_drv_init_video` (1488) reaches `uvc_drv_init_cur_fmt_frm` (1580) as
soon as the device answers GET_DEF or GET_CUR probe control requests —
which a malicious device does by definition. Config-descriptor budget:
256 × 11-byte VS_FORMAT_MJPEG = 2816 bytes (+ headers = 2869 total),
far inside the 65535-byte `wTotalLength` limit. No privilege, no user
interaction: the trigger is device attach.

## What the rig replaces (and why)

The QEMU guest has no emulated UVC webcam (QEMU ships no such device), so
the rig fabricates `struct usb_device` (`->cdesc`, `->ifaces`,
`->ifaces_max`) and calls the production parse/attach functions with it.
Everything from descriptor walk to the panic is in-tree code. The two
`usbd_*` helpers used (`usbd_find_descriptor`, `usb_desc_foreach`) only
read `udev->cdesc`/`udev->ifaces` — both attacker-controlled at attach
time in production.

## Fix validation (same rig, patched production sources)

After applying `fix.diff` (uint16_t fields + NULL guards), the identical
trigger prints `stored data->nfmt=256` and returns EINVAL from
`uvc_drv_init_cur_fmt_frm` — no panic (see `fix_run.log`). Baseline
panic vs patched clean return, same binary rig, same crafted descriptors.

## Impact

Deterministic kernel NULL-deref panic (local DoS) at USB device attach;
requires attacker-controlled USB hardware (or USB passthrough of a
crafted device). No memory-disclosure or control-flow effect: `fmt` is
NULL, the read is at address 0x8 — pure denial of service. Matches the
finding's Medium severity.

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