DF-2778 / verdict.json
{ "finding_id": "DF-2778", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "Deterministic unprivileged UAF: mq_notify stores an unreferenced struct proc * (sys_mqueue.c:988); fo_close (and thus the clearing at :388-389) runs only at last file reference (kern_descrip.c:3180), so a registrant that exits while a fork child holds the descriptor leaves mq_notify_proc dangling after wait4 frees the proc (kern_exit.c:1336, kfree M_PROC general heap). The next send on the empty queue executes PHOLD(freed) (kern_proc.c:341) and ksignal(freed) (sys_mqueue.c:899-906) — reproduced round-0 in every mode: an innocent sleeping victim was signalled/killed (incl. SIGKILL) through the freed chunk its own fork recycled, proving atomic writes (p_lock), token ops and sigset bit-sets on freed memory plus arbitrary-process signal injection. uid0 chain not completed this run; primitives characterized in VERDICT.md.", "exploit_chain": "mq_open+fork -> child registers SIGEV_SIGNAL notify and exits while parent holds fd -> wait4 frees child proc -> fork victim recycles chunk -> parent mq_send -> PHOLD/PRELE (atomic add at p_lock offset) + lwpsignal on freed/recycled chunk (p_token acquire, SIGADDSET_ATOMIC on p_siglist) -> alien signal (up to SIGKILL) delivered to the victim process; repeatable; general-heap reuse allows aiming fixed-offset atomic writes at arbitrary same-size-class objects.", "evidence": ["run.log: round-0 HIT in all 3 modes incl. '[VICTIM] *** ALIEN SIGUSR1 RECEIVED THROUGH FREED struct proc ***'", "run.2.log: SIGKILL variant 'victim killed by alien sig 9'", "panic not required; dmesg core-dump line in run.log notes", "fix_validation.log: 0 hits in 220 rounds on patched kernel"], "kernel_refs": ["sys/kern/sys_mqueue.c:988", "sys/kern/sys_mqueue.c:388-389", "sys/kern/sys_mqueue.c:875-885", "sys/kern/sys_mqueue.c:899-906", "sys/kern/kern_descrip.c:3180-3181", "sys/kern/kern_exit.c:1336", "sys/kern/kern_proc.c:339-342", "sys/kern/kern_fork.c:551"], "poc_changes": "Used raw syscall(SYS_mq_*) instead of any libc mqueue API; pipe-synchronized fork ordering (registrant exits while parent holds the fd); victim forks immediately after reap to recycle the freed proc chunk; three modes (SIGUSR1 kill, handler print, SIGKILL).", "attempts": 3, "guest_uname": "DragonFly dfbsd 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": 95, "guest_dirty": 0, "build_cmd": "cc -O2 -pthread -o uaf_notify uaf_notify.c", "run_cmd": "./uaf_notify 96 10; ./uaf_notify 8; ./uaf_notify 4 9", "code_hash": "8f430b7eca7bd9d1e449b1a5675e9e65a96a939fb560c1bc92422d131ca6d157", "notes": "100% reproduction rate (round 0 every run). Guest kernel has INVARIANTS (config X86_64_GENERIC); no assert fires on this path - the corruption is silent, which is why the alien-signal observable was engineered.", "recommended_fix": "Clear mq_notify registrations at registrant exit: mqueue_proc_exit() walk from exit1 (fix.diff), or per-proc list of registered queues (NetBSD model); never store an unreferenced proc pointer.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Tue Sep 1 04:57:57 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Patched kernel (mqueue_proc_exit called from exit1 after fdfree): identical PoC ran 116 + 104 rounds across all modes with zero hits (victims undisturbed every round), guest stable. Baseline hit round 0 every run.", "fix_evidence": "fix_validation.log; /tmp/kbuild4.log BUILD-OK on guest" } |