snp: snoop line discipline installable via unprivileged TIOCSETD; snplclose NULL-derefs t_sc on tty close (kernel panic)
Summary
TIOCSETD accepts any discipline index with no privilege check, and the snoop discipline's l_open is plain ttyopen which never sets t_sc - closing such a tty runs snplclose which does snp_down(NULL), faulting at offset 0x30. Any local user triggers it on a private pty once snp.ko is loaded; /dev/snp perms irrelevant (device never opened). REPRODUCED on guest as uid 1001 (Fatal trap 12, fault VA 0x30, Stopped at snp_down+0x73) and FIX-VALIDATED. Guard-t_sc-NULL fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4668 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| snp_disc.c | β | 2.3 KB | view raw | |
| build.sh | β | 101 B | view raw | |
| run.sh | β | 140 B | view raw | |
| run.log | β | 2.1 KB | view raw | |
| panic.txt | β | 1.6 KB | view raw | |
| env.txt | β | 424 B | view raw | |
| VERDICT.md | β | 3.1 KB | β raw | |
| fix.diff | β | 1.5 KB | view raw | |
| manifest.json | β | 796 B | view raw | |
| verdict.json | β | 3.2 KB | view raw |
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_discas 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)
sys/kern/tty_pty.c:406β pty slave close calls(*linesw[tp->t_line].l_close)(tp, flag)βsnplclosewhen the discipline is the snoop one.sys/kern/tty.c:1128-1145β TIOCSETD accepts ANYt < nlineswwith no privilege check (nlinesw = MAXLDISC = 9,sys/kern/tty_conf.c:79).sys/kern/tty_conf.c:93-115βldisc_register(LDISC_LOAD, &snpdisc)fromsnp_modevent(snp.c:721) installs snoop in the last free slot 7..8; itsl_open(ttyopen, snp.c:69) never setstp->t_sc, sot_scstays NULL.sys/dev/misc/snp/snp.c:135-137βsnplclosedoessnp = tp->t_sc; error = snp_down(snp);with no NULL check.sys/dev/misc/snp/snp.c:517βsnp_down(NULL)readssnp->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.
Fix verification
fixedfix.diff applied to guest /usr/src, kernel rebuilt (make nativekernel RC=0) and installed (#1 Tue Sep 22 18:38:10 UTC 2026), snp.ko loaded; exact same PoC run as unprivileged maxx: 9 line disciplines found, snoop-disc pty slave closed, no panic, exit=0, guest up. Baseline panic vs patched survive both captured (panic.txt / run.2.log).
run.2.log (patched run output); panic.txt (baseline); fix.diff; build.log
Confirmed kernel references
Detail
Exploit chain
unprivileged user (uid 1001, no groups) -> TIOCSETD(own pty, disc 8=snooplinedisc) -> close(slave) -> ptsclose -> snplclose with t_sc==NULL -> snp_down(NULL) -> deterministic kernel panic (DoS). No privesc: the faulting pointer is a constant NULL.
Evidence (decisive lines)
panic.txt: 'fault virtual address = 0x30' + 'Stopped at snp_down+0x73: cmpq $0x1000,0x30(%rbx)' (offset 0x30 = offsetof(struct snoop, snp_blen); $0x1000 = SNOOP_MINLEN). run.2.log: same trigger on patched kernel survives.
PoC changes
Seed sketch fixed: (1) enumeration must distinguish l_open failure (nodisc slots 1-6) from ENXIO (t >= nlinesw) and confirm install via TIOCGETD - snp occupies slot 7/8, so the highest installing index is snooplinedisc; (2) include
Verified recommended fix
snplclose: if tp->t_sc == NULL (discipline installed via TIOCSETD without SNPSTTY attach), skip snp_down and just run ttylclose under tp->t_token.
Verdict
Unprivileged kernel NULL-deref panic reproduced on the stock 6.5-DEVELOPMENT guest: with snp.ko loaded, any user installs the snoop line discipline on a private pty via TIOCSETD (tty.c:1128 has no privilege check; ttyopen() never sets t_sc), and closing the tty runs snplclose -> snp_down(NULL) -> read at NULL+0x30 (snp_blen). Fatal trap 12, 'Stopped at snp_down+0x73: cmpq $0x1000,0x30(%rbx)'. Fixed kernel rebuilt with the guard in snplclose: identical run survives, exit=0, guest stays up.
No comments yet.