⬢ 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": "Round 2 (weaponization mode) terminal: uid=0 is NOT achievable from DF-2687 on the stock single-devfs guest, blocked structurally rather than probabilistically. (1) Pin invariant measured: the orphans leaked t_pgrp pgref pins the sessions pgrp, which pins the session (s_count) - vmstat -m shows M_SESSION in-use +8 while 8 orphan slaves are held and back to baseline only at their closes; the session frees INSIDE the orphans own ttyclearsession (pgrel->sess_rele->kfree) ~10 instructions before the same function reads tp->t_session->s_prg. There is no persistent reclaim window and no sleep point after the free, so the dangling deref always consumes stale-but-valid contents (s_prg = real procglob) and close() returns cleanly - 3+8+8750 more closes in round 2 with zero panics, matching round 1s 40 surviving rounds. The round-1 deterministic panic requires the root-set INVARIANTS-only debug.use_weird_array poison (kern_slaballoc.c:1433-1438); zero-precondition unprivileged impact is silent. (2) The recycled-session-hijack route is closed: 0/256 setsid candidates ever aliased the orphan (TIOCGSID oracle never fired), consistent with the pin model and with round 1s own logs. (3) A real unprivileged controlled-content reclaim primitive was built (sysctl KERN_PROC_ARGS self-write = kmalloc(104)+96 controlled bytes+kfree, same slab zone as struct session) and raced against the window for 8750 attempts with a persistent non-destructive fake-token trace detector: 0 hits, explained by DFly LWKT not switching user threads mid-syscall and nothing interrupt-side allocating the 104-byte zone. (4) Even with full chunk control the sole deterministic consumer is the lwkt_gettoken/lwkt_reltoken pair on one controlled s_prg value - by construction net-zero (reversible CAS/clear; contended path only self-DoSes); the powerful sinks (ttymodem ksignal(s_leader), sess_rele LIST_REMOVE + ttyunhold(s_ttyp)->t_unhold function-pointer call, pgrel write through pg_session->s_ttyp) are each unreachable: ksignal is gated by master-reopen fencing (no /dev/ptm) and ordering, and the sess_rele/pgrel-on-controlled-chunk sinks require the session/pgrp to free while still referenced, which the pin invariant plus live-process pgrp pinning prohibit in every ordering traced. Round-1 panic and fix validation stand unchanged.",
  "exploit_chain": "attempted: unpriv orphan chain -> hold -> KERN_PROC_ARGS 104-byte controlled reclaim churn (df2687race.c) -> close(orphan) racing the ~30ns same-syscall window between kfree(session) and the sp->s_prg read -> forged s_prg = userland fake lwkt token (no SMAP) -> persistent trace t_count 2->0. Result: 0/8750 hits; window is structurally uninterleavable by userland (no mid-syscall user-thread switches, no interrupt-side 104-zone allocations, no sleep points post-free). uid0 chain not reachable: sink inventory for the dangling t_session on stock config = {net-zero token dance} only; ttymodem ksignal gated; sess_rele/pgrel controlled-chunk sinks impossible while pin invariants hold",
  "evidence": [
    "run.pin.log: BEFORE=19 MIDHOLD=27 AFTER=19 M_SESSION in-use -> session chunks pinned while orphan slaves held, freed exactly at close",
    "run.alias.log: 0/256 setsid candidates aliased the orphan -> recycled-session hijack impossible",
    "run.race.log: df2687race 3 8 60 -> ~8750 window attempts, 0 fake-token trace hits, plus structural analysis of why",
    "run.poison1.log: poison sysctl inert on non-INVARIANTS snapshot #1; bug presence confirmed behaviorally via pin experiment",
    "panic.txt / run.log (round 1): deterministic INVARIANTS-poison panic at lwkt_gettoken+0x64, unprivileged trigger",
    "df2687race.c / df2687x.c: round-2 sources (race weaponizer with fake-token trace detector; pin/alias probes)"
  ],
  "kernel_refs": [
    "sys/kern/tty.c:1181-1204",
    "sys/kern/tty.c:279-316",
    "sys/kern/tty.c:294-296",
    "sys/kern/tty.c:1580-1589",
    "sys/kern/tty.c:1623-1624",
    "sys/kern/kern_proc.c:649-692",
    "sys/kern/kern_proc.c:776-807",
    "sys/kern/kern_proc.c:896-948",
    "sys/kern/kern_proc.c:1955-1976",
    "sys/kern/kern_slaballoc.c:1433-1438",
    "sys/kern/lwkt_token.c:262-296",
    "sys/kern/lwkt_token.c:439-459"
  ],
  "poc_changes": "Round 2 added: df2687x.c (pin/alias/spray probes) and df2687race.c (weaponization race: 3 closer threads x orphan chains + 8 sprayer threads x KERN_PROC_ARGS 104-byte controlled transient alloc churn; benign persistent-trace detector via userland fake lwkt token with t_count=TOK_EXCLREQ transitioning 2->0 on consumption; no crash needed to detect a hit). Round-1 trigger unchanged.",
  "attempts": 9,
  "guest_uname": "round 1: DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (INVARIANTS, panic + fix validation) / round 2: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly v6.5.0.1712.g89e6a-DEVELOPMENT #1: Mon Jun 29 14:18:01 UTC 2026 x86_64 (non-INVARIANTS reset snapshot; pin/alias/race experiments)",
  "runtime_sec": 3600,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -Wall -o /tmp/df2687x df2687x.c ; cc -O2 -Wall -o /tmp/df2687race df2687race.c -lpthread",
  "run_cmd": "/tmp/df2687x pin 8 6 (with root-side vmstat -m session sampler) ; /tmp/df2687x alias 5 ; /tmp/df2687race 3 8 60   (all as uid 1001, no preconditions)",
  "code_hash": "b548388699d368dd256a27410505cea7489aa377e75ad847f8f5be9169f8d26e",
  "notes": "uid0: NOT achieved; terminal structural blocker (not effort- or luck-limited): (a) pgrp-pins-session invariant chains the session free into the orphans own close, leaving a ~10-instruction same-thread window with no sleep points; (b) DFly LWKT does not preempt user threads mid-syscall and nothing interrupt-side allocates the 104-byte kmalloc zone, so userland cannot place content in the window (0/8750 race attempts); (c) the sole deterministic sink (lwkt_gettoken/reltoken on controlled s_prg) is net-zero by design; (d) ttymodem ksignal sink gated (no /dev/ptm on stock; DF-2769 fence); (e) sess_rele/pgrel LIST_REMOVE + ttyunhold t_unhold-call sinks require free-while-referenced orderings that the refcount graph prohibits (a live processs p->p_session can never dangle). Zero-precondition unpriv impact on default config: silent (stale-valid read). Deterministic panic only with root-set INVARIANTS poison. Strongest theoretical primitive if the window were winnable: arbitrary-address atomic RMW (OR TOK_EXCLREQ=2 / CAS set-bit0 / conditional -4) plus transient kernel-td_toks-pointer write to a user-chosen address - still not a uid0 primitive on its own. DF-5816/DF-2702 heap-address leaks considered: useless without any write sink. Fix from round 1 (dissociate replaced tty in TIOCSCTTY + reject D_MASTER fds) remains correct; kern_proc.c KERN_PROC_ARGS transient alloc is noted as a generic 104-byte controlled-churn primitive for future slab-zone work.",
  "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)"
}