DF-2687 / run.race.log
# DF-2687 round 2 — weaponization race (guest snapshot #1, 2026-10-16) # Attempt to land attacker-controlled content in the freed struct session # chunk inside the same-syscall window of the orphan's final close. # # Reclaim primitive (unprivileged, zero-precondition): # sysctl KERN_PROC_ARGS self-write: # kmalloc(sizeof(struct pargs)+newlen = 8+96 = 104, M_PARGS) # -> same per-size slab zone as struct session (104 bytes, # zoneindex 12; zones are shared across malloc types) # SYSCTL_IN(96 fully controlled binary bytes) (kern_proc.c:1962-1971) # kfree(pa) (transient; freed chunk retains the bytes) # s_prg (+24) sprayed with the VA of a private userland fake lwkt token # (no SMAP on this guest). Fake token t_count = TOK_EXCLREQ(2). # If the kernel's ttyclearsession() token dance consumes the sprayed # chunk: gettoken CAS 2->1 + t_ref:=<kernel ptr>, reltoken -> t_count 0. # Persistent trace: page transitions 2 -> 0. No crash expected. $ cc -O2 -Wall -o /tmp/df2687race df2687race.c -lpthread $ /tmp/df2687race 3 8 60 (as maxx, uid 1001) fake token page at 0x100000000 t_count=2 closer 0: 1024 closes, 0 hits closer 1: 1024 closes, 0 hits closer 2: 1024 closes, 0 hits closer 1: 2048 closes, 0 hits closer 0: 2048 closes, 0 hits closer 2: 2048 closes, 0 hits closer 0 DONE: 2913 closes, 0 hits closer 1 DONE: 2981 closes, 0 hits closer 2 DONE: 2858 closes, 0 hits RESULT: hits=0 fake-token final t_count=2 RACE_RC=3 # ~8750 window attempts, 0 hits. This is structural, not bad luck: # # 1. The window (kfree(session) at the tail of sess_rele()->pgrel() ... # to the `prg = sp->s_prg` read, same thread, same ttyclearsession # invocation) contains no sleep/block points: after kfree(sess) the # code path is lwkt_reltoken + kfree(pgrp) + two returns + a branch. # # 2. DragonFly LWKT does not preempt a user thread mid-syscall with # another user thread; user threads switch only at block/yield/syscall # boundaries. Only interrupt context / higher-priority kernel threads # can interleave, and nothing interrupt-side allocates from the # 104-byte kmalloc zone. # # 3. Chunks freed to a CPU's zone array are only handed out by subsequent # allocations on that same CPU (or a remote drain), which cannot be # triggered by userland inside the window. # # Therefore the dangling t_session deref on a stock configuration always # consumes the pre-free (stale but valid) session contents: s_prg is # still the real procglob pointer, the token dance is a no-op, and close() # returns cleanly. Empirically consistent: round-1's 40 unpoisoned # rounds survived, plus 3 + 8 + 8750 more closes in round 2, zero panics. # The deterministic panic requires the INVARIANTS-only # debug.use_weird_array=1 poison (a root-set debugging aid). # # And even with full chunk control the ceiling is zero: the only # deterministic consumer of the dangling pointer is # lwkt_gettoken(&sp->s_prg->proc_token) / lwkt_reltoken() # which is by construction reversible (net-zero RMW at one controlled # address; the contended path only ORs TOK_EXCLREQ and blocks the closer # itself = DoS). The powerful sinks (ttymodem()'s # ksignal(sp->s_leader) at tty.c:1623; sess_rele()'s LIST_REMOVE + # ttyunhold(tp->s_ttyp) -> tp->t_unhold function-pointer call; # pgrel()'s write through pg_session->s_ttyp) are all unreachable: # - ksignal: gated behind master carrier loss; unix98 masters are # devfs-invisible after device close on stock single-devfs mounts # (no /dev/ptm in this guest; DF-2769's own control run was FENCED). # - sess_rele/pgrel on a controlled chunk: the session cannot free # while the orphan's leaked t_pgrp pgref pins its pgrp, and a live # process's own pgrp always pins its session, so p->p_session of a # live process can never dangle either.