# DF-2352 — division by zero in `uvc_drv_try_frame_interval` (continuous intervals)

`sys/bus/u4b/uvc/uvc_drv.c` — for VS frame descriptors with
`bFrameIntervalType == 0` (continuous interval frames),
`uvc_drv_parse_data()` reserves 3 interval slots (`nitv += 3` at
uvc_drv.c:1705-1706) and skips them during the fill loop
(`if (frm->interval_type == 0) itv += 3;` uvc_drv.c:1922-1923) **without
reading the `dFrameInterval[]` min/max/step DWORDs that the UVC spec
requires the descriptor to carry**. The slots stay zero (M_ZERO
allocation). `uvc_drv_try_frame_interval()`'s continuous branch then
computes `(interval - min + step/2) / step` with `step == 0`
(uvc_drv.c:499-504) — integer divide fault.

Trigger: any local user, `ioctl(VIDIOC_S_PARM)` on `/dev/video0` (mode
0666, created by `uvc_v4l2_reg`), on a device whose descriptors contain a
continuous-interval frame. The division happens *before* any USB traffic
in `uvc_drv_set_streampar()` (uvc_drv.c:1386-1387), so the trigger is a
single unprivileged ioctl.

## Evidence class

Production-handler KLD dispatch rig (`rig/uvc_rig.c` includes the
**unmodified** production uvc sources). Root fabricates the malicious
device state (descriptors parsed by production code; `/dev/video0`
registered by production `uvc_v4l2_reg()`); the fatal ioctl is issued by
an **unprivileged user** (`trigger_sparm.c`, uid 1001) through the
production `uvc_v4l2_ioctl` → `uvc_drv_set_streampar` →
`uvc_drv_try_frame_interval` chain.

## Build / run

```
./build.sh        # push rig + triggers, build uvc_rig.ko and trigger_sparm
./run.sh          # arm via sysctl hw.usb.uvcrig=2352, then run_user /tmp/trigger_sparm
```

## Expected output (see panic.txt)

```
RIG2352: frame0 interval_type=0, slots after production uvc_drv_parse_data: min=0 max=0 step=0
RIG2352: (descriptor carried min=2000000 max=10000000 step=3333 -- all-zero slots prove the parse skip at uvc_drv.c:1922)
uvc_v4l2_set_streamparm
Setting frame interval to 1/30 (333333).
Fatal trap 18: integer divide fault while in kernel mode
Stopped at      uvc_drv_try_frame_interval.isra.2+0x60: divl %r8d,%eax
```

`trap 18` is x86 #DE (divide error); the faulting instruction is the
`/step` division at uvc_drv.c:504.

## Fix validation

`fix.diff` (1) reads the min/max/step DWORDs for continuous frames in
`uvc_drv_parse_data()` (when `bLength >= 38`, else safe defaults of 1),
and (2) guards `step == 0` (and clamps) in `uvc_drv_try_frame_interval()`.
On the patched rig (`fix_run.log`):

```
RIG2352direct: step slot=3333 ... returned 3333200 -- NO DIV-BY-ZERO
RIG2352: ... slots after production uvc_drv_parse_data: min=2000000 max=10000000 step=3333
trigger_sparm: ioctl returned 0 errno=0        <- full unprivileged path, no panic
uvc_drv_do_request-1-USB_ERR_NOT_CONFIGURED   <- rig's fake transport, graceful
```
