# VERDICT — DF-2778: mq_notify_proc dangling `struct proc *` (UAF)

**Status: REPRODUCED** · impact: dos (cross-process signal injection through freed
kernel heap + UAF atomic writes; full uid0 chain not developed this run) ·
confidence: certain

## What was proven on the guest (stock kernel, build #0 2026-07-02)

`./uaf_notify` hit on **round 0, every run, in all three modes**:

1. `signo=10` (SIGUSR1): innocent sleeping victim killed by alien SIGUSR1 —
   dmesg: `pid 880 (uaf_notify), uid 1001: exited on signal 10 (core dumped)`.
2. handler mode: `[VICTIM] *** ALIEN SIGUSR1 RECEIVED THROUGH FREED struct proc ***`
3. `signo=9`: innocent victim killed by alien **SIGKILL** — cross-process
   signal-kill injection through a freed struct proc.

Mechanism (all unprivileged, deterministic, no race needed):
- registrant R registers SIGEV_SIGNAL via its inherited fd (`sys_mqueue.c:988`,
  raw pointer, no PHOLD), exits while parent M still holds the descriptor;
- M's `wait4(R)` frees R's `struct proc` (`kern_exit.c:1336`, kfree M_PROC —
  general kernel heap) — `mq_close_fop` never ran for R because it only runs at
  LAST file reference (`kern_descrip.c:3180-3181`) and M still holds one;
- M forks victim V whose proc allocation recycles R's chunk (same CPU, LIFO
  magazine — hence round-0 hits);
- M's `mq_send` on the empty queue executes the notify path
  (`sys_mqueue.c:875-885`): `notify = mq_notify_proc` (freed), then
  `PHOLD(notify)` = `atomic_add_int(&p->p_lock, 1)` on freed memory
  (`kern_proc.c:341`), then `ksignal(notify, signo)` → `lwpsignal` takes
  `lwkt_gettoken(&p->p_token)`, reads `p_flags`/`p_sigignore`, executes
  `SIGADDSET_ATOMIC(p->p_siglist, sig)` — all on the recycled chunk — and the
  signal is delivered to V, the process now occupying the freed object.

## Exploitability assessment

Primitive inventory per trigger: (a) two atomic-increment writes at fixed
offset `p_lock`; (b) lwkt token acquire/release writes on the freed chunk
(`p_token`); (c) atomic bit-set write at `p_siglist.__bits[k]`, k
signo-derived; (d) cross-process delivery of an arbitrary valid signal
(1..63) to whichever process reoccupies the chunk — demonstrated kill of an
arbitrary innocent process. Because `struct proc` is served from the general
kernel heap (`kmalloc(..., M_PROC, ...)`), a groomed attacker can aim these
fixed-offset writes at a same-size-class victim object of a *different* type
and the SIGKILL at a privileged process. A complete uid0 chain (slab groom →
victim credential-adjacent object → controlled field corruption) was not
developed in this run; the demonstrated deterministic UAF-write + arbitrary
process kill stands on its own as High severity.

## Fix validation (patched kernel, build #1 2026-09-01 04:57)

`fix.diff` adds `mqueue_proc_exit()` (clears registrations at exit1, after
fdfree). Same PoC, patched kernel: **0 hits in 116 rounds (build #1 04:10)
plus 104 rounds (build #1 04:57)** across all three modes — victims
"undisturbed" every round, guest stable. Baseline vs patched in
`fix_validation.log`.

fix_status: **fixed** (fix_baseline_reproduced=1, fix_patched_reproduced=0).
