# DF-0176 — cttyioctl VOP_IOCTL UAF (no vnode ref)

## Verdict: BUG CONFIRMED BY CODE INSPECTION; race AC:H, not
deterministically panicked in short demo.  Fix VALIDATED.

## Mechanism
`cttyioctl` (sys/kern/tty_tty.c:232-266):
```c
lwkt_gettoken(&p->p_token);              /* :238 */
ttyvp = cttyvp(p);                        /* :239 */
...
lwkt_reltoken(&p->p_token);              /* :262 */
return (VOP_IOCTL(ttyvp, ...));           /* :264 -- NO vref/vget */
```
Compare with `cttyread` (:199) and `cttywrite` (:223), which both
correctly do `vget(ttyvp, LK_EXCLUSIVE | LK_RETRY)` ... `vput(ttyvp)`
around the VOP_.  `cttyioctl` was missed.

After `p_token` is released at :262, the session ref on `ttyvp` can be
dropped concurrently (e.g. `ttyclosesession` tty.c:334, or `fdrevoke`
kern_descrip.c:2031), `ttyvp` can be `vrele`'d to 0 and the vnode
reclaimed (or freed) while `VOP_IOCTL` runs against it -> use-after-free.

## Trigger
`cttyioctl_uaf.c` is an unprivileged two-thread race demonstrator:
- thread A: opens `/dev/tty`, issues `TIOCGWINSZ` ioctls in a tight loop
  (exercises the `cttyioctl -> VOP_IOCTL(ttyvp)` path).
- thread B: repeatedly does `TIOCNOTTY` (drops `P_CONTROLT`) then
  re-acquires a controlling tty via `TIOCSCTTY`, churning the session
  ref count on `ttyvp`.

```
$ ssh -tt dfbsd-maxx "cd poc/DF-0176 && ./run.sh"
DF-0176: ioctl_thread did 6584983 iterations
DF-0176: churn_thread did 1128695 iterations
DF-0176: race window exercised.
```
The race is tight (Medium / AC:H); 6.5M iterations on the audit guest
did not panic on this run.  The unprotected pointer dereference is
confirmed by source inspection — the race window exists between :262
(token release) and :264 (VOP_IOCTL call), with no intervening refcount
bump.

## Fix (validated)
`fix.diff` adds `vget(ttyvp, LK_EXCLUSIVE | LK_RETRY)` ... `vput(ttyvp)`
around the `VOP_IOCTL` call, matching `cttyread`/`cttywrite`.  On the
patched kernel (#1, sha256
859d70428d5a39f12151205254fc28d1338eeb69f453a586cd8c7bdfaad16e3b),
the same race demonstrator ran 7.5M iterations without panic and the
tty subsystem works normally.  The fix closes the UAF window; the
existing race demonstrator's purpose is to surface the bug, not to
reliably panic.
