DragonFlyBSD Kernel Audit
DF-2980 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2980",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "none",
  "confidence": "certain",
  "verdict": "addupc_task() (sys/kern/subr_prof.c:145) calls stopprofclock() without p->p_token, violating the documented contract (kern_clock.c:1303-1305); the resulting non-atomic p->p_flags &= ~P_PROFIL runs at syscall rate via userret (trap.c:231) whenever profil(2) is armed with an unmapped sample base, and a tokenless stale store was reproduced (2/3 runs, sub-2 s) annihilating PT_DETACH's token-held P_TRACED clear on the stock INVARIANTS guest by an unprivileged user. Demonstrated impact is kernel process-state corruption of security-gated bits (P_TRACED; same defect races |= P_SUGID at kern_prot.c:1304 which gates ptrace attach at sys_process.c:202-204 - speculative uid0 chain, not demonstrated); no memory corruption (copy targets are VM_MAXUSER_ADDRESS-checked). fix.diff (take p->p_token around the stopprofclock call) rebuilt as kernel #1: profiling still works and 3x1,000,000 cycles (~10x baseline exposure) produced zero hits.",
  "exploit_chain": "unpriv: loop profil(2) with pr_base in unmapped user VA -> every syscall exit runs userret->addupc_task->copyin(EFAULT)->stopprofclock (tokenless p_flags RMW at MHz) -> stale store annihilates a concurrent token-held RMW: reproduced on PT_DETACH's &=~(P_TRACED|P_WAITED) (P_TRACED resurrected on a running child, state-verified via kinfo kp_flags) and expected on |= P_TRACED (PT_DETACH EPERM) and |= P_SUGID during setuid exec (kern_exec.c:493) -> P_SUGID loss would let a same-ruid parent PT_ATTACH a setuid-root image (sys_process.c:202-204) -> PT_WRITE code injection -> uid0 (speculative; not demonstrated)",
  "evidence": "run.log (HIT2 after 99310 cycles/1.9s, kp_stat=2 SACTIVE kp_flags=0x1c20 P_TRACED resurrected), run.3.log (HIT2 after 16166 cycles/0.33s, kp_flags=0xc20), run.2.log (300K-cycle no-hit - probabilistic), fix-patched.log (3x1M cycles zero hits on patched kernel #1), sanity-baseline.log, build.log, VERDICT.md full narrative",
  "kernel_refs": [
    "sys/kern/subr_prof.c:145",
    "sys/kern/kern_clock.c:1303-1311",
    "sys/platform/pc64/x86_64/trap.c:225-232",
    "sys/platform/pc64/x86_64/trap.c:500-510",
    "sys/kern/sys_process.c:268-271",
    "sys/kern/sys_process.c:305",
    "sys/kern/sys_process.c:362",
    "sys/kern/kern_prot.c:1298-1307",
    "sys/kern/kern_exec.c:493",
    "sys/sys/proc.h:241,353",
    "sys/kern/kern_synch.c:1368"
  ],
  "poc_changes": "Seed concept kept, execution rewritten: victim uses an unmapped 0x400000000000 window with 4 GiB size and 1.0 scale (copyin fault on every userret -> tokenless stopprofclock per syscall exit); tracer cycles PT_ATTACH/waitpid/PT_DETACH (naive re-attach gets EBUSY - relationship persists); multi-threaded victim straggler tstop() re-clears P_WAITED (kern_synch.c:1368) causing a benign DETACH EBUSY artifact, handled by re-consuming the re-posted stop with waitpid(WNOHANG|WUNTRACED); two false-positive-free hit signatures (DETACH EPERM = annihilated set; ATTACH EBUSY after successful detach = annihilated clear) plus kinfo kp_stat/kp_flags state dump for attribution.",
  "attempts": 6,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "runtime_sec": 900,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -Wall -pthread -o poc poc.c",
  "run_cmd": "./poc 300000",
  "code_hash": "165f28e1ea5b15624f5f351ab407a5894a7873d06e4174d2c0447411f027e3f5",
  "notes": "Race is probabilistic: 2/3 baseline runs hit (1.9s, 0.33s), one 300K-cycle run clean; patched kernel clean across 3x1M cycles. Guest reset to pristine baseline after fix validation (verified kernel #0 and pristine subr_prof.c md5 974b1875e9983feeb4595c295cd0ad40). Fix kernel uname: DragonFly 6.5-DEVELOPMENT #1: Fri Sep  4 14:50:31 UTC 2026. Broader hardening noted: p_flags RMW writers are not mutually serialized (tstop clears P_WAITED under parent token vs ptrace under child token); making p_flags updates atomic would close the whole class.",
  "recommended_fix": "Hold p->p_token across the stopprofclock() call in addupc_task() (sys/kern/subr_prof.c:145), per stopprofclock()'s documented locking contract.",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Fri Sep  4 14:50:31 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Applied fix.diff (lwkt_gettoken/reltoken around stopprofclock in addupc_task) to a clean /usr/src in the guest, make -j6 nativekernel + installkernel, rebooted into kernel #1. sanity.c confirms profiling still accumulates samples; the exact PoC ran 3 x 1,000,000 attach/detach cycles (~49 s, ~10x the exposure in which the baseline hit twice) with ZERO hits, versus baseline hits at 16166 and 99310 cycles. Bad behavior gone, no regression.",
  "fix_evidence": "fix.diff, fix-patched.log (3x1M cycles clean + sanity pass on #1), fix-kernel-uname.txt, baseline run.log/run.3.log (hits on #0)"
}