# DF-1295 — cxm METEORSSIGNAL stores curproc without PHOLD → UAF

## Finding
`cxm_ioctl` (METEORSSIGNAL) at `sys/dev/video/cxm/cxm.c:2760` stores
`curproc` into `sc->enc_proc` with **no PHOLD**:
```c
sc->enc_proc = sig ? curproc : NULL;
```
The stored pointer is consumed asynchronously from the encoder-DMA interrupt
at `cxm.c:1363-1364`:
```c
if (sc->enc_proc)
    ksignal(sc->enc_proc, sc->enc_signal);
```
`ksignal` → `lwpsignal` (`sys/kern/kern_sig.c:1150`) does `PHOLD(p)` which
increments `p->p_lock` — **a write into the proc struct**.

`sc->enc_proc` is cleared only in `cxm_close` (`cxm.c:2197`). The
exploitation pattern: a child process registers via `METEORSSIGNAL` then
`exit`s while the parent keeps the device fd open. `cxm_close` is **not**
called for the child (the fd is still open in the parent), so `sc->enc_proc`
retains a pointer to the child's now-freed `struct proc`. The next DMA
interrupt calls `ksignal` on the freed slab ⇒ **UAF write**.

## Why we did not reproduce at runtime
The `cxm` driver attaches only to Conexant CX23416/CX2388x PCI video capture
cards. The audit guest has no such device. The driver is built as a module
(not in X86_64_GENERIC, not loaded — `kldstat` shows no `cxm`). Without the
device, `/dev/cxm0` does not exist and `METEORSSIGNAL` is unreachable.

The brief claims `/dev/cxm0` is mode 0444 — that is the default cdev mode
for cxm; with the card present, an unprivileged user can `open()` (read-only
satisfies the permission check) and issue `METEORSSIGNAL` via `ioctl`. The
trigger is therefore plausibly unprivileged **when the card is present**.

## Source-level confirmation
- `cxm.c:2760` — `sc->enc_proc = sig ? curproc : NULL;` — no PHOLD.
- `cxm.c:2761` — `sc->enc_signal = sig;`
- `cxm.c:1363-1364` — `if (sc->enc_proc) ksignal(sc->enc_proc, sc->enc_signal);` — async consumer.
- `kern_sig.c:1118-1121` — `ksignal(p, sig) { lwpsignal(p, NULL, sig); }`.
- `kern_sig.c:1150` — `PHOLD(p);` inside `lwpsignal` — write to `p->p_lock`.
- `cxm.c:2197` — `sc->enc_proc = NULL;` only on `cxm_close`.
- `cxm.h:235` — `struct proc *enc_proc;` — raw pointer, no refcount.

The bug is the missing `PHOLD`/`PRELE` (or pid_t + pfind) lifecycle.
Confirmed real.

## Realistic impact ceiling
UAF write (atomic increment of `p_lock`) on a freed `struct proc` slab.
Could be groomed into a more useful primitive on a non-debug kernel; on
default GENERIC (INVARIANTS ON) the slab poisoning (`WEIRD_ADDR 0xdeadc0de`)
would catch the deref of a freed proc and panic before exploitation lands.
Preconditions: cxm card present + admin-chowned `/dev/cxm0` (or default 0444
sufficient for read-only open) + fork/exit dance. Plausibly unprivileged
local → kernel memory corruption when the HW is present.

## Fix
`fix.diff` adds a `PHOLD(curproc)` when storing and `PRELE(sc->enc_proc)`
when clearing in both `cxm_ioctl(METEORSSIGNAL)` and `cxm_close`. This pins
the proc struct until the driver is done with it, eliminating the UAF. The
alternative (pid_t + pfind/PRELE at delivery) is more invasive; the
PHOLD/PRELE pair is the minimal correct fix and matches the pattern used by
`sigio` in `kern/kern_sig.c`.
