DF-2980 / verdict.json
{ "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)" } |