# DF-5305 — strlcpy() from non-NUL-terminated device GUID: heap OOB read past the cdesc allocation, disclosed to unprivileged userspace via VIDIOC_ENUM_FMT

## Where

`sys/bus/u4b/uvc/uvc_drv.c:1888-1890` (`uvc_drv_parse_data()`, case
`UDESCSUB_VS_FORMAT_FRAME_BASED`):

```c
} else {
	strlcpy(fmt->name,
		(const char *)(fbraw->bGuidFmt),
		sizeof(fmt->name));          /* <-- bGuidFmt is 16 raw bytes */
	fmt->fcc = 0;
}
```

`bGuidFmt` is a 16-byte raw GUID inside the class-specific format
descriptor — it is **not** a C string.  `strlcpy()` walks `src` until a
NUL byte to compute `strlen()`, then copies up to 63 bytes into
`fmt->name[64]`.  A device that fills the GUID and the trailing
descriptor bytes with non-zero values makes the walk run off the end of
the descriptor, through the rest of the `kmalloc()`ed config-descriptor
buffer (`udev->cdesc`, allocated with exactly `wTotalLength` bytes,
`sys/bus/u4b/usb_request.c:1366`), and into adjacent kernel heap —
reading kernel memory until the first NUL.

`fmt->name` is then handed to unprivileged userspace by
`uvc_drv_enum_v4l2_fmt()` (`uvc_drv.c:833`,
`strlcpy(vfmt->description, fmt->name, sizeof(vfmt->description))`,
31 bytes) — reachable with `VIDIOC_ENUM_FMT` on `/dev/videoN`, which
`uvc_v4l2_reg()` creates **mode 0666** (`uvc_v4l2.c:783-785`).

## Attack model

Crafted USB webcam descriptors (evil-USB device; QEMU-emulatable).  The
device controls the leak *length* (non-NUL run) and the leaked *window*
(placement of the format descriptor near the end of `cdesc`); any local
user can then read the leaked bytes.

## Build / run (as verified on the QEMU guest)

    ./build.sh          # KLD harness that #includes the REAL uvc sources
    ./run.sh            # kldload -> 3-round leak test at load

The harness (`uvcharness.c`) compiles the *actual*
`uvc_buf.c/uvc_ctrls.c/uvc_v4l2.c/uvc_drv.c` from `/usr/src` (driver
registration neutered) and drives the real `uvc_drv_parse_ctrl()` /
`uvc_drv_parse_data()` / `uvc_drv_enum_v4l2_fmt()` with a crafted
72-byte config blob whose last bytes are non-NUL, so `strlcpy()` must
cross the allocation boundary to terminate.  A groomed marker chunk
(0x43/0x45/0x47 per round) is placed after the blob.

## Expected (baseline, unpatched)

    HARNESS[1] DF-5305: name strlen=63; in-bounds non-NUL run ... is 27 bytes
    HARNESS[1] DF-5305: *** OOB READ PAST ALLOCATION CONFIRMED: 36 bytes from outside the blob ***
    HARNESS[1] DF-5305: uvc_drv_enum_v4l2_fmt ret=0 -> user-visible description=<...ZZZZCCCC>

36 bytes read from beyond the 72-byte `cdesc` allocation land in
`fmt->name`; the final `CCCC` in the user-visible description are those
leaked heap bytes.

## After fix.diff (memcpy of exactly 16 GUID bytes + NUL)

    HARNESS[1] DF-5305: name strlen=16; ...
    user-visible description=<16 GUID bytes only>

## Files

    uvcharness.c   harness source (#includes the real driver code)
    Makefile       guest KLD makefile
    build.sh/run.sh
    build.log      compiler output (successful build)
    run.log        baseline: 3 rounds, OOB confirmed each time
    run.2.log      earlier 68-byte-blob run (walk crossed, first OOB byte was 0)
    run_patched.log / fix.log   post-fix runs: strlen=16, no OOB
    fix.diff       git-apply-able unified diff (git apply -p0)
    leak_sample.txt extracted fmt->name across the 3 baseline rounds
    env.txt        guest uname / kldstat
