⬢ DragonFlyBSD Kernel Audit
DF-2687 / run.race.log
← back to finding ↓ download raw
# 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.