# DF-2351 — uint16→uint8 truncation of nfmt → NULL-deref panic at device attach

`sys/bus/u4b/uvc/uvc_drv.c` — `uvc_drv_init_data_fmt()` counts formats/frames/
intervals into `uint16_t` locals (uvc_drv.c:1674) but stores them into the
`uint8_t` fields `data->nfmt/nfrm/nitv` (uvc_drv.h:662-665, store at
uvc_drv.c:1737-1739). With exactly 256 `VS_FORMAT_*` descriptors the
allocation is sized with the full count (correct, 24 KB) but
`data->nfmt == 0`. The later default-format search in
`uvc_drv_init_cur_fmt_frm()` (`for (i = v->data->nfmt; i > 0; i--)`,
uvc_drv.c:1457) never runs, `fmt` stays NULL, and `fmt->nfrm`
(uvc_drv.c:1463) dereferences NULL.

Threat: a malicious USB webcam whose configuration descriptor contains 256
format descriptors — deterministic kernel panic at attach, no user
interaction beyond plugging (or USB passthrough) in.

## Evidence class

Production-handler KLD dispatch rig: the four **unmodified** production
uvc driver sources are `#include`d into `uvc_rig.ko` (see `rig/uvc_rig.c`),
so the descriptor parsing exercised below is the exact in-tree code. The
rig synthesizes only the USB transport (a fake `struct usb_device` whose
`->cdesc` is the crafted config descriptor a malicious webcam reports and
whose `->ifaces[1].idesc` points at the VideoStreaming interface
descriptor) — no emulated UVC webcam exists for this QEMU guest.

## Build / run

```
./build.sh                       # push rig + triggers to guest, build uvc_rig.ko (gcc 8.3, -Werror)
./run.sh                         # reset guest, load rig, sysctl hw.usb.uvcrig=2351
```

## Expected output (decisive lines, see panic.txt)

```
RIG2351: production uvc_drv_init_data_fmt ret=0
RIG2351: counted nfmt=256 (uint16_t local) -> stored data->nfmt=0 nfrm=0 nitv=0 fmt=0xfffff80118496000  <-- TRUNCATION
RIG2351: production uvc_drv_parse_data ret=0 (data->nfmt=0)
RIG2351: entering production uvc_drv_init_cur_fmt_frm ...
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0x8
Stopped at      uvc_drv_init_cur_fmt_frm.cold.36:       movq    0x8,%rax
```

`fault virtual address = 0x8` is exactly `offsetof(struct uvc_data_format,
nfrm)` from a NULL `fmt`.

## Fix validation

`fix.diff` widens `nfmt/nfrm/nitv` to `uint16_t` and adds NULL-fmt guards
in `uvc_drv_init_cur_fmt_frm()` and the `uvc_drv_init_video()` GET_CUR
fallback. Re-running the identical trigger on the patched rig
(`fix_run.log`):

```
RIG2351: ... stored data->nfmt=256 ... fmt=0xfffff80118660000
No frame desc found for default format.
RIG2351: init_cur_fmt_frm returned 22 -- NO PANIC
```
