# DF-5434 — VERDICT

**Status: reproduced (kernel UAF write + OOB write / page fault). Fix validated.**

## What was run

`bufharness.ko` compiles the **unmodified production sources**
(`uvc_buf.c`, `uvc_v4l2.c`, `uvc_drv.c`, `uvc_ctrls.c` from
`/usr/src/sys/bus/u4b/uvc/`) into a KLD, registers the production
character device via the real `uvc_v4l2_reg()` (`make_dev(..., 0666,
"video96")`), and drives the **production** completion function
`uvc_bulkbuf_sell_buf()` (uvc_buf.c:222-312) from kernel context with a
fabricated `usb_page_cache` (`pc->buffer` set, zeroed otherwise ⇒
"Case 2 - Plain PIO" in `usbd_get_page()`, usb_busdma.c:118-123 — the
exact copy primitive the USB callback uses).  The only fabricated state
is what a streaming camera would otherwise provide (frame payloads, FID
toggle); every victim code path is production code.

## Primitive A — deterministic kernel UAF write (modes 1/2/3)

Sequence (all production calls):
`uvc_buf_queue_req_bufs(4, 0x10000)` → `uvc_buf_queue_queue_buf(index=3)` →
bulk sells fill buf3 (ACTIVE, bytesused=0x1000, product head) →
**`uvc_buf_queue_req_bufs(1, 0x10000)` while the queue is live** →
`uvc_buf_queue_free_bufs_locked()` (uvc_buf.c:517-525) frees the 0x40000
block, sets `buf_count=1`, re-inits only descriptor 0, drains nothing →
descriptor 3 still holds `mem=<freed>`, `status=ACTIVE`, and is still
`STAILQ_FIRST(&bq->product)` (log: `product head=... == buf3? YES`) →
next bulk payload: `usbd_copy_out(pc, 0, mem_freed + 0x30000 + 0x1000,
0x800)` (uvc_buf.c:268-270) writes **0x800 bytes of device-controlled
frame data into freed kernel memory**.

Observed (run.modes123.log, three independent rounds/markers):

```
A1: UAF-WRITE PROVEN: found 2048 bytes of 0xa5 pattern inside freed/reclaimed block 0xfffff80118760000
A2: UAF-WRITE PROVEN: found 2048 bytes of 0x58 pattern ... 0xfffff80118788000
A3: UAF-WRITE PROVEN: found 2048 bytes of 0x3c pattern ... 0xfffff80118788000
```

2048 = exactly the 0x800-byte payload written at the predicted offset
`OLDmem+0x31000`.  In two later independent vulnerable builds
(run.vuln2.log, run.vuln3.log) the same sequence left the guest dead on
the dangling state (`Fatal trap 12 ... Stopped at mode_a+0x342` while
touching the freed block) — the driver's own stale pointers.

No race is involved: everything runs under `bq->mtx`; the bug is the
*state* left behind by the free path, not a locking window.  An
unprivileged user reaches it on any streaming UVC camera with plain V4L2
ioctls (open 0666 /dev/videoN → S_FMT/REQBUFS (pri) → STREAMON → QBUF →
frames flow → REQBUFS(smaller count) — the v4l2 layer at uvc_v4l2.c:613
never checks streaming state).

## Primitive B — stale-bytesused underflow OOB write (mode 4)

`req_bufs(2, 0x2000000)` → sells accumulate `buf0.vbuf.bytesused =
0x2000000` (mid-frame, ACTIVE) → `req_bufs(2, 0x1000)` (smaller frame
size: in production `len = UGETDW(v->req.dwMaxFrameSize)`, uvc_v4l2.c:620,
which `VIDIOC_S_PARM` refreshes from the **device's probe response**
mid-stream — uvc_drv.c:1385-1393 `probe = v->req; uvc_drv_probe_video();
v->req = probe;` — or a malicious device controls directly) → descriptor
0 is re-inited with `length=0x1000` but **`bytesused` stays 0x2000000**
(`uvc_buf_fill_v4l2()`, uvc_buf.c:91-102, never clears it) → next payload:
`maxlen = 0x1000 - 0x2000000 = 0xE0001000` (uvc_buf.c:265, underflow) →
`nbytes = min(maxlen, len) = len` → `usbd_copy_out` writes at
`mem2 + 0x2000000` in a 0x2000-byte allocation.

Observed (panic.txt) — the harness prints the predicted VA *before*
triggering:

```
PREDICTED fault VA = 0xfffff8011a7cc000 (mem2 + stale bytesused 0x2000000)
Fatal trap 12: page fault while in kernel mode
fault virtual address   = 0xfffff8011a7cc000      <-- EXACT MATCH
fault code              = supervisor write data, page not present
Stopped at      memcpy+0xfa:    repe movsq        <-- inside usbd_copy_out
```

Offset and extent of the OOB write are chosen by the previous session's
accumulated `bytesused` (device/frame-size controlled) — a controlled
kernel heap overflow primitive, not just a crash.

## Killed candidate (documented, Info-level, NOT the High finding)

The signed-count path (`num = *count` as `int`, uvc_buf.c:549; clamp at
:550 signed; alloc loop at :558 skipped for `num<0`; `mem` uninitialized
at :564) was hypothesized to leave a wild `bq->mem` + sign-extended
`buf_count`.  Guest `objdump` of the built module proves the shipping
gcc 8.3 **folds the uninitialized read into a deterministic ENOMEM**
(`172a: movslq %r12d,%rdi; jg 1635` … `1688: mov $0xc,%r12d`), and mode 8
confirms at runtime: `req_bufs(0x80000000) -> ENOMEM ... bq->mem=0
buf_count=0 unchanged`.  No memory-safety impact on this toolchain;
filed separately as hardening (the fix.diff clamp removes the UB).

## Fix validation (fix.diff applied to guest /usr/src, KLD force-rebuilt)

* modes 1/2/3: `buf3 NOT re-inited: mem=0 status=0 bytesused=0x0; product
  head=0` → `no hit: found 0 bytes` **in all three rounds** (fix_run.log).
* mode 4: `buf0: length=0x1000 bytesused=STALE 0x0` — the stale bytesused
  is gone; write is in-bounds; no fault; guest up.
* mode 8 + unpriv `trig5434` (uid 1001): `REQBUFS(0x80000000) ret=0
  count=0x8` (clamped, V4L2-correct), every OOB `QUERYBUF(8..23)`/`QBUF(16)`
  returns `EINVAL` (fix_run_user.log).
* `vm.sh status` ⇒ up after the full fixed sequence.

Baseline bad behaviors (UAF pattern bytes, predicted-VA fault) are gone.

## Exploitability (honest ceiling)

Write primitive: device-frame-data content (semi-controlled: a camera
looking at a controlled pattern; fully controlled for a malicious USB
device) at a chosen offset into freshly freed M_UVC memory of
attacker-selected size class (count × frame size).  Classic path to
uid=0 is slab-grooming a victim object into the freed block and
overwriting a function pointer / cred-adjacent structure; not developed
to uid0 here because the guest has no UVC hardware — the KLD rig proves
the kernel-side primitive with production code, which is the same code a
real camera exercises.  Requires a UVC device present and `hw.usb.v4l2.
v4l2_not_allowed == 0` (default).

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