# DF-5434 — UVC buffer-queue realloc UAF/OOB write (uvc_buf.c)

## Finding

`VIDIOC_REQBUFS` is accepted **while the buffer queue is live** (no
streaming-state check at `sys/bus/u4b/uvc/uvc_v4l2.c:613-630`, none in
`uvc_buf_queue_req_bufs()` at `sys/bus/u4b/uvc/uvc_buf.c:537`).  The realloc
path calls `uvc_buf_queue_free_bufs_locked()`
(`sys/bus/u4b/uvc/uvc_buf.c:517-525`), which frees `bq->mem` but leaves

* the `product`/`consumer` STAILQs **undrained**, and
* every `bq->buf[i]` descriptor **un-reset** (`mem`, `vbuf.bytesused`,
  `status`, link pointers all stale).

The USB completion engine (`uvc_bulkbuf_sell_buf` / `uvc_buf_sell_buf`)
walks `STAILQ_FIRST(&bq->product)` and copies frame data through those
stale descriptors:

* **(A) count-shrink UAF write** — descriptors with `index >= new count`
  keep `mem` pointing at the **freed** block while remaining product-head:
  `usbd_copy_out()` writes device frame data into freed kernel memory
  (`uvc_buf.c:268-270`, `uvc_buf.c:346-348`).  Deterministic, no race.
* **(B) len-shrink OOB write** — the re-inited descriptor keeps a stale
  `vbuf.bytesused` (`uvc_buf_fill_v4l2()`, `uvc_buf.c:91-102`, never clears
  it); `maxlen = length - bytesused` (`uvc_buf.c:265`) **underflows**,
  `nbytes = min(maxlen, len)` becomes `len`, and the copy lands at
  `mem_new + stale_bytesused` — an out-of-bounds heap write at a
  fully attacker/device-chosen offset.

Not the same as DF-1065 (userspace **mmap** of freed pages): the victim
here is the kernel's own completion path writing through stale
driver-internal descriptors.

## How to reproduce (guest, root for rig setup; trigger itself is uid-1001-capable)

```
# build (NOTE: uvc_buf.c is #included by bufharness.c and NOT in SRCS —
# always force a full rebuild after touching it:  rm -f *.o *.ko)
sh build.sh                     # KLD from pristine /usr/src uvc sources

# baseline (vulnerable):
kldload /root/build5434/bufharness.ko
sysctl hw.bufharness=1          # mode A: UAF write proof (marker 0x5A)
sysctl hw.bufharness=2          # marker 0xA7
sysctl hw.bufharness=3          # marker 0xC3
sysctl hw.bufharness=8          # signed-count probe (ENOMEM fold)
sysctl hw.bufharness=4          # mode B: underflow OOB write -> PANIC
                                # (predicted fault VA printed first)

# fix validation:
cd /usr/src && patch -p0 < fix.diff
cd /root/build5434 && cp /usr/src/sys/bus/u4b/uvc/uvc_buf.c . && rm -f *.o *.ko && make
(reboot) ; kldload ...; sysctl hw.bufharness=1..4,8   # all clean, guest stays up
/tmp/trig5434                                        # unpriv: count clamps to 8,
                                                     # QUERYBUF/QBUF OOB -> EINVAL
```

## Expected output (vulnerable)

* mode 1/2/3: `HARNESS DF-5434[A*]: UAF-WRITE PROVEN: found 2048 bytes of
  0x?? pattern inside freed/reclaimed block ...` (0x800 payload bytes found
  at OLDmem+0x31000) — kernel wrote through the stale descriptor.
* mode 4: `PREDICTED fault VA = 0xfffff801...` then
  `Fatal trap 12 ... fault virtual address = <same VA>`,
  `supervisor write data`, `Stopped at memcpy+0xfa` (inside usbd_copy_out).

## Expected output (fixed)

* modes 1-3: `buf3 NOT re-inited: mem=0 status=0 bytesused=0x0; product
  head=0` and `no hit: found 0 bytes` — descriptors reset, queues drained,
  no write into freed memory.
* mode 4: `buf0: length=0x1000 bytesused=STALE 0x0` — stale bytesused is
  gone; in-bounds write; no fault, guest stays up.
* mode 8 / trig5434: `REQBUFS(0x80000000)` succeeds with `count=0x8`
  (clamped); every out-of-bounds QUERYBUF/QBUF returns EINVAL.

## Files

* `bufharness.c`  — KLD: real uvc sources compiled in (DRIVER_MODULE
  neutered), production `uvc_v4l2_reg()` creates a real 0666
  `/dev/video96`, sysctl drives the production completion path
  (`uvc_bulkbuf_sell_buf`) with a fabricated `usb_page_cache` exactly as
  `uvc_drv_bulkfill_buf()` does from the xfer callback.
* `trig5434.c`    — unprivileged userland probe (uid 1001 used): the
  signed-count candidate REQBUFS(0x80000000) + OOB index sweep (negative
  evidence: on this toolchain the uninit `mem` read compiles to ENOMEM —
  see `VERDICT.md`).
* `run.modes123.log` — vulnerable: 3× UAF-WRITE PROVEN + ENOMEM fold.
* `panic.txt`     — vulnerable: mode B fault at the predicted VA.
* `run.vuln2.log`, `run.vuln3.log` — two further vulnerable runs
  (independent builds/reboots): stale descriptors + UAF write, guest dies
  on the dangling state.
* `fix_run.log`, `fix_run_user.log` — fixed build: all modes clean.
* `build.log`, `fix_build.log`, `env.txt`, `fix.diff`.
