# DF-2352 — VERDICT: REPRODUCED (panic, unprivileged ioctl) — FIX VALIDATED

## Bottom line

Both halves of the finding were proven in one run:

1. **Parse skip**: the kernel console shows the production
   `uvc_drv_parse_data()` leaving the interval slots of a
   `bFrameIntervalType == 0` frame at `min=0 max=0 step=0` even though the
   crafted descriptor carried `min=2000000 max=10000000 step=3333` — the
   DWORDs are simply never read (uvc_drv.c:1917-1923 only walks
   `interval_type` entries, then `itv += 3` past the M_ZERO slots).
2. **Divide by zero**: an **unprivileged** user (uid 1001) issued
   `ioctl(VIDIOC_S_PARM)` on `/dev/video0` (mode 0666, registered by
   production `uvc_v4l2_reg`, uvc_v4l2.c:783-785). The production dispatch
   chain `uvc_v4l2_ioctl` (uvc_v4l2.c:503-508) → `uvc_drv_set_streampar`
   (uvc_drv.c:1370-1387) → `uvc_drv_try_frame_interval` (uvc_drv.c:480)
   executed the continuous branch:

```
uvc_v4l2_set_streamparm                       <- production kprintf, uvc_drv.c:1377
Setting frame interval to 1/30 (333333).      <- production kprintf, uvc_drv.c:1382
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 = x86 #DE; the faulting instruction is the `/ step` at
uvc_drv.c:504. Guest panicked (ssh died, vm.sh status = down).

## Line-by-line trace

- uvc_drv.c:1705-1706 — `nitv += (bFrameIntervalType > 0) ? ... : 3`
  (slots reserved for continuous frames)
- uvc_drv.c:1917-1921 — fill loop runs only `interval_type` times
- uvc_drv.c:1922-1923 — `if (frm->interval_type == 0) itv += 3;`
  (dFrameInterval[0..2] min/max/step NEVER read — slots stay 0)
- uvc_drv.c:500-504 — `step = frame->interval[2].val;` (=0) then
  `interval = min + (interval - min + step/2) / step * step;` → #DE
- Reachability: the S_PARM path divides BEFORE any USB control request
  (uvc_drv.c:1386-1387 precedes `uvc_drv_probe_video` at 1389), so no
  live device response is needed at trigger time — only that
  `v->cur_frm` was set to a continuous frame at attach (which the
  production `uvc_drv_init_cur_fmt_frm` does from the device's own
  default-format answer; attacker-controlled).
- `/dev/videoN` is created 0666 by `uvc_v4l2_reg` (uvc_v4l2.c:783-785);
  `v4l2_not_allowed` defaults to 0 (uvc_v4l2.c:66-72) — any local user
  can open it. (The finding said "mode 0666" — verified.)

## Rig-synthesized parts

The malicious descriptor blob and the answered GET_DEF/GET_CUR probe
state (fabricated by calling production `uvc_drv_init_cur_fmt_frm`
directly) — in production both come from the device. The ioctl path from
`/dev/video0` down to the division is 100 % production code. The guest
has no emulated UVC webcam, which is the only reason the hardware layer
is synthesized.

Note on the trigger source: DragonFly's `VIDIOC_S_PARM` is
`_IOWR('V', 22, ...)` (sys/contrib/v4l/videodev2.h:1830), not Linux's 75
— `trigger_sparm.c` mirrors the kernel header exactly.

## Fix validation

`fix.diff`: read min/max/step for continuous frames when
`bLength >= 26+12` (usb_desc_foreach guarantees those bytes are inside
the validated descriptor), else safe fallback 1/1/1; plus a `step == 0`
guard and min/max clamps in `uvc_drv_try_frame_interval()`. On the
patched rig (fix_build.log / fix_run.log):

- direct call: `step slot=3333`, `uvc_drv_try_frame_interval` returned
  3333200 — no #DE;
- full unprivileged `VIDIOC_S_PARM`: `ioctl returned 0`, no panic; the
  follow-on probe request fails gracefully against the rig's fake
  transport (`USB_ERR_NOT_CONFIGURED` — a rig artifact; a real device
  would answer).

## Impact

Deterministic kernel panic (local DoS) from a single unprivileged ioctl
on any UVC device exposing a continuous-interval frame — a descriptor
shape fully controlled by a malicious/faulty webcam. Matches the
finding's Medium severity (attacker-controlled-hardware precondition for
the descriptor half; the trigger itself needs no privilege).

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