DragonFlyBSD Kernel Audit
DF-2687 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2687",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "Deterministic unprivileged kernel use-after-free reproduced on the stock INVARIANTS guest: TIOCSCTTY on a pty MASTER fd makes the master vnode the session's ctty vnode; its last close clears s_ttyvp (devfs half-close) without ttyclose(), so tp->t_session survives; the leader then re-TIOCSCTTYs a second tty and exits; sess_rele() frees the session leaving the first tty's t_session dangling (proven non-NULL in kern.ttys for a TS_ZOMBIE orphan with refs=0); closing the orphaned slave dereferences sp->s_prg in ttyclearsession() and lwkt_gettoken() GP-faults on the INVARIANTS poison 0xdeadc0de (Fatal trap 9, lwkt_gettoken+0x64, pid of unprivileged PoC). Without the poison flag the same deref silently consumes freed-memory contents (ksignal-through-freed-s_leader reachable in configurations where the master can be re-opened). fix.diff hunk 1 (dissociate replaced tty in TIOCSCTTY) built, installed and verified: no panic and kern.ttys shows session=0x0/pgrp=0x0 for the orphan.",
  "exploit_chain": "unprivileged setsid+ptmx -> TIOCSCTTY(master fd) -> close(master) clears s_ttyvp w/o ttyclose -> TIOCSCTTY(pts2) orphans tp1 (t_session kept, s_ttyp moved, ttyunhold) -> leader exit kfrees session -> tp1->t_session dangles -> close(orphan slave) -> ttyclose->ttyclearsession->s_prg->lwkt_gettoken on freed memory -> Fatal trap 9 panic; escalation ceiling (not completed): M_SESSION chunk recycle makes tp1->t_session alias a chosen live session (ctty hijack, cross-session TIOCSPGRP/TIOCSWINSZ pgsignal, ttymodem ksignal(s_leader) where master reopen is possible)",
  "evidence": [
    "panic.txt: 'Fatal trap 9 ... Stopped at lwkt_gettoken+0x64: movq (%r12),%rax' with current process = unprivileged df2687",
    "forensic.baseline.txt: orphaned tty state=01100020 refs=0 session=0xfffff80116d4e970 (non-NULL dangling) in kern.ttys while the session is freed",
    "forensic.fixed.txt: same orphan state after fix -> pgrp=0x0 session=0x0",
    "run.log / run.fix.log: baseline panic vs patched 3 clean rounds with debug.use_weird_array=1",
    "build.log: nativekernel BUILD_RC=0 (357s) + installkernel for the fix validation"
  ],
  "kernel_refs": [
    "sys/kern/tty.c:1181-1204",
    "sys/kern/tty.c:279-316",
    "sys/kern/tty.c:294-296",
    "sys/kern/tty.c:1623-1624",
    "sys/kern/tty.c:246-266",
    "sys/vfs/devfs/devfs_vnops.c:1598-1611",
    "sys/vfs/devfs/devfs_vnops.c:1146-1156",
    "sys/kern/tty_pty.c:636-689",
    "sys/kern/kern_proc.c:896-948"
  ],
  "poc_changes": "Replaced TIOCGPTN (EAGAIN-gated before slave open on DF) with fstat(st_rdev) minor unit discovery; fixed pipe end mixup; kept slave fd open in parent across leader exit (prevents VOP_CLOSE from running ttyclose early); added hold-mode for kern.ttys forensics; final trigger is the slave close (ttyclearsession sink) after enabling debug.use_weird_array poison; session-alias candidate swarm retained for the TIOCGSID oracle.",
  "attempts": 8,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline, panic) / #1: Sun Aug 30 20:17:44 UTC 2026 (fix validation)",
  "runtime_sec": 4200,
  "guest_dirty": 1,
  "build_cmd": "cc -O2 -Wall -o /tmp/df2687 /home/pocs/df2687.c ; cc -O1 -D_KERNEL_STRUCTURES -o /tmp/forensic /home/pocs/forensic.c",
  "run_cmd": "sysctl debug.use_weird_array=1 && su -m pocs -c '/tmp/df2687 1'   (panic on stock; clean on patched)",
  "code_hash": "942cefd9f3959ad541857208350b068c4976c98a5a1efd161f4c084eefe5bc69",
  "notes": "debug.use_weird_array (INVARIANTS, default 0) only poisons freed slab chunks so the unconditional UAF read faults observably; it does not create the bug. unix98 master cdevs are devfs-invisible after device close on the stock guest, which gates the ttymodem/ksignal sink here but not in configs with a second devfs mount (jails). Same-type LIFO recycle of the 104-byte M_SESSION chunk yields session aliasing (ctty hijack). Also leaks a pgrp reference per round (t_pgrp of the orphan). Guest reset to clean-source snapshot after validation.",
  "recommended_fix": "TIOCSCTTY: fully dissociate the replaced tty (clear t_session/t_pgrp under its token, pgrel) before ttyunhold(); reject TIOCSCTTY on D_MASTER fds in devfs_fo_ioctl/vn_ioctl before the device ioctl runs (fix.diff)",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Sun Aug 30 20:17:44 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Kernel rebuilt with fix.diff hunk 1 (sys/kern/tty.c TIOCSCTTY dissociation) only; exact same PoC with debug.use_weird_array=1 completes all rounds with no panic; kern.ttys forensic shows the orphaned tty fully dissociated (session=0x0, pgrp=0x0) where the stock kernel showed the dangling session pointer and panicked.",
  "fix_evidence": "run.fix.log; forensic.fixed.txt; build.log (nativekernel BUILD_RC=0, installkernel INSTALL_RC=0)"
}