# DF-4668 — VERDICT

**Status: reproduced. Impact: panic (unprivileged local kernel DoS). Confidence: certain.**

## What was run

- Guest: DragonFly 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026, kernel
  X86_64_GENERIC, QEMU/KVM (dfbsd-qemu), fresh `vm.sh reset with-src`.
- Setup (root): `kldload snp` (module shipped at /boot/kernel/snp.ko;
  /dev/snp* created 0600 root:wheel — trigger does NOT touch those nodes).
- Build (unpriv user `maxx`, uid 1001, not in wheel):
  `cc -O1 -o /tmp/snp_disc /tmp/snp_disc.c -lutil` — clean.
- Run: `/tmp/snp_disc` as maxx. ssh connection died mid-run; `vm.sh status`
  => down; serial console (`vm.sh log`) shows the panic below.

## Decisive evidence (panic.txt)

```
Fatal user address access from kernel mode from snp_disc at ffffffff826005b3
Fatal trap 12: page fault while in kernel mode
fault virtual address  = 0x30
fault code             = supervisor read data, page not present
Stopped at      snp_down+0x73:  cmpq    $0x1000,0x30(%rbx)
```

`0x30` = 48 = `offsetof(struct snoop, snp_blen)` (list 16 + target 8 + tty 8 +
len 8 + base 8); `$0x1000` = SNOOP_MINLEN. The faulting instruction is
literally `snp.c:517` (`if (snp->snp_blen != SNOOP_MINLEN)`) with
`snp == tp->t_sc == NULL`, reached from `snplclose` on tty close.

## Why it reproduces (path:line trace)

1. `sys/kern/tty_pty.c:406` — pty slave close calls
   `(*linesw[tp->t_line].l_close)(tp, flag)` → `snplclose` when the discipline
   is the snoop one.
2. `sys/kern/tty.c:1128-1145` — TIOCSETD accepts ANY `t < nlinesw` with no
   privilege check (`nlinesw = MAXLDISC = 9`, `sys/kern/tty_conf.c:79`).
3. `sys/kern/tty_conf.c:93-115` — `ldisc_register(LDISC_LOAD, &snpdisc)` from
   `snp_modevent` (snp.c:721) installs snoop in the last free slot 7..8; its
   `l_open` (ttyopen, snp.c:69) never sets `tp->t_sc`, so `t_sc` stays NULL.
4. `sys/dev/misc/snp/snp.c:135-137` — `snplclose` does
   `snp = tp->t_sc; error = snp_down(snp);` with no NULL check.
5. `sys/dev/misc/snp/snp.c:517` — `snp_down(NULL)` reads `snp->snp_blen`
   → fatal page fault at 0x30.

PoC fixes vs the seed sketch: the enumeration must distinguish "l_open failed"
(nodisc slots return error) from "beyond nlinesw" (ENXIO) — slots 1..6 fail in
`l_noopen`, slot 7/8 (snoop, ttyopen) succeed; TIOCGETD confirms the install.

## Impact ceiling

Pure NULL-pointer read at a fixed kernel address → deterministic panic from
any local user once the snp module is loaded (admin ran watch(1)/kldload).
No corruption, no info leak — a reliable unprivileged DoS. Not reachable to
privesc (the faulting data is a constant NULL).

## Fix validation

`fix.diff` (generated against read-only sys/, applied to the guest's /usr/src
copy, kernel rebuilt with `make nativekernel`): re-running the exact PoC —
see verdict.json `fix_*` fields and run.2.log in this pack.

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