# DF-2680 — VERDICT

**status: reproduced**, **impact: dos** (kernel UAF write; benign-but-
visible manifestation), **confidence: certain**.

## Reproduced how

1. **Control (live path)** — `victim2` (root) opens `/dev/devctl`,
   sets `FIOASYNC=1` with a SIGIO handler installed, and stays alive.
   Loading the dfrace churn module (512 device attaches → `devadded` →
   `devaddq` → `devctl_queue_data`) delivers SIGIO:
   `victim2: GOT_SIGIO (live path works) pid=1961`.
   (Necessary because SIGIO's default disposition in DragonFly is
   *ignore* — a handler is required to observe delivery.)

2. **Stale path** — `victim` (root) sets `FIOASYNC=1` and exits
   without clearing it. Its `struct proc` is reaped and freed
   (kern_exit.c:1336). `devclose()` leaves `devsoftc.async_proc`
   pointing at the freed chunk. The next fork — a `catcher` process
   that never opened `/dev/devctl` — recycles the chunk. Firing devctl
   events then executes `ksignal(async_proc, SIGIO)` on the recycled
   memory and the **catcher receives SIGIO**:

   ```
   victim: pid=1993 enabled FIOASYNC on /dev/devctl, exiting
   GOT_SIGIO pid=1994 (never opened devctl)
   ```

   5/5 rounds reproduced (run.log).

## Why this is the bug and not something else

- `devopen()` clears `async_proc` on the *next* open, so during the
  window the pointer is exactly the exited victim's proc.
- The catcher demonstrably never opened `/dev/devctl`; SIGIO can only
  have arrived via `devctl_queue_data()`'s `ksignal(devsoftc.async_proc)`.
- `lwpsignal()` wrote `PHOLD(p)` / token state into the recycled chunk
  before delivering — i.e. kernel writes through a dangling pointer
  occurred; the signal delivery is merely the visible edge.

## Impact ceiling

If the freed chunk is recycled by a *non-proc* kernel allocation (any
M_PROC-bucket-sized object) instead of a new process, `PHOLD(p)` (a
refcount increment at the chunk's `p_lock` offset) and
`lwkt_gettoken(&p->p_token)` corrupt that object. No escalation chain
was developed because the setup itself requires root (devopen is
SYSCAP_RESTRICTEDROOT-gated and the node is 0600), so the finding is
filed Low severity; the demonstrated runtime effect is cross-process
signal delivery through freed kernel memory (a correctness/security
defect any devd-adjacent root daemon can trip over by exiting while
FIOASYNC is set).

## Fix validation

Not rebuilt for this Low-severity finding (fix is a two-line close-path
clear + optional PHOLD/PRELE); `fix.diff` provided for the record and
reasoned through: clearing `async_proc` under `devsoftc.lock` in
`devclose` removes the dangling target; `devopen` already clears it on
reuse.
