# DF-3941 VERDICT — cxm_read() ring race

## Bottom line

**inconclusive at runtime (hardware absent on the audit guest); the defect is
proven at source level.** The guest has no Conexant CX23415/16 PCI device, so
the trigger cannot execute past `open("/dev/cxm0")` here. The analysis below
traces the bug line-by-line and shows why it is real on any machine with the
card.

## The bug

`/dev/cxmN` is created world-readable (`make_dev(..., 0444, ...)`,
cxm.c:1885-1886) and `cxm_open()` refuses only a *second open*
(`sc->is_opened`, cxm.c:2158-2161). One open file descriptor shared across
`fork()` therefore gives any unprivileged user N concurrent `cxm_read()`
callers on the same `softc` — and nothing in the driver serializes them:

* the only synchronization in `cxm_read()` is the `crit_enter()` block
  (cxm.c:2244-2260) covering the `tsleep()` and the *snapshot* of
  `buffers_available`;
* the entire consumption loop (fixup + `uiomove`, cxm.c:2262-2288) and the
  `enc_pool.offset`/`enc_pool.read` bookkeeping (cxm.c:2290-2296) run
  **unlocked**;
* `enc_pool.offset` is a *single* `size_t` shared by the whole ring
  (cxm.h:74), and the pair (offset, read) is stored non-atomically at
  cxm.c:2290-2296.

### Primitive 1 — offset/buffer mismatch → size_t underflow → OOB read

A partial read of buffer *i* leaves `offset = X < size_i` paired with
`read = i`. A racing context that then consumes buffer *i* fully stores
`offset = 0` and advances `read → j`. A third context that read
`sc->enc_pool.offset` (cxm.c:2262) **before** that store but evaluates the
loop initializer `i = sc->enc_pool.read` (cxm.c:2264) **after** the advance
holds the mismatched pair (X, j) and computes

```c
nbytes = sc->enc_pool.bufs[current].size - offset;   /* cxm.c:2270 */
```

which **underflows** (both `size_t`, cxm.h:66) whenever `X > size_j`
(buffer sizes are per-DMA-request values, `min(req.size, CXM_SG_SEGMENT)`,
cxm.c:1485-1487 — they differ between buffers and need not be multiples of
anything). The only clamp is against the *user's* request length
(cxm.c:2273-2274), so

```c
uiomove(sc->enc_pool.bufs[current].vaddr + offset, nbytes, ap->a_uio);  /* :2276 */
```

copies up to `uio_resid` bytes starting at `vaddr + X` — past the
61440-byte (`CXM_SG_SEGMENT`, cxm.h:183) `bus_dmamem_alloc`'d DMA buffer —
into userspace: a **kernel-heap disclosure via read(2)**.

### Primitive 2 — read/write desync → uninitialized kernel heap disclosure

Two racing readers can both snapshot `buffers_available` and both advance
`sc->enc_pool.read` (cxm.c:2293-2296), pushing `read` past `write`
(subsequent `buffers_available = write - read (+CXM_SG_BUFFERS)`,
cxm.c:2257-2259). The same desync arises when `METEORCAPTUR`
STOP_CONT/CONTINOUS (cxm.c:2555-2569) races an in-flight read:
`cxm_start_encoder()` resets `offset/read/write` (cxm.c:1119-1121) while the
reader's stale locals overwrite them afterwards. read() then returns buffers
**never written by DMA** — the buffers are `bus_dmamem_alloc()`'d and never
zeroed (cxm.c:1806-1829), i.e. leftover kernel heap.

Both primitives are the same root cause: the ring state machine is
unserialized.

## Why it did not run here

```
[-] open(/dev/cxm0): No such file or directory
```
Guest checks (run.log / env.txt): no 14f1:8800/8802 PCI device, no cxm KLD,
no /dev/cxm*, driver not in X86_64_GENERIC. The full trigger is built and
kept in this pack for hardware-equipped verification.

## Fix validation

`fix.diff` adds a driver `struct lock` serializing the ring consumption and
the two state resets (start_encoder, close), plus a defensive
`offset > bufs[i].size` clamp. Compile-validated on the guest's kernel tree
with `-Werror` (fix_build.log: cxm.ko relinked, rc=0). Runtime
baseline/patched comparison is impossible without the card —
`fix_status: not_testable` for that reason only.

## Kernel references

* sys/dev/video/cxm/cxm.c:2211-2299 — cxm_read(), no serialization
* sys/dev/video/cxm/cxm.c:2270 — underflowing subtraction
* sys/dev/video/cxm/cxm.c:2276 — unbounded uiomove after clamp
* sys/dev/video/cxm/cxm.c:1119-1121 — unlocked reset racing readers
* sys/dev/video/cxm/cxm.c:2158-2161, 1885-1886 — fd-sharing not prevented, 0444 device
* sys/dev/video/cxm/cxm.c:1801-1829 — uninitialized DMA buffers
* sys/dev/video/cxm/cxm.h:66,70,74,183 — size_t size, ring, shared offset, CXM_SG_SEGMENT

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