DF-2691 / verdict.json
{ "finding_id": "DF-2691", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Unprivileged use-after-free reproduced twice on fresh boots of the stock INVARIANTS kernel: an EVFILT_SIGNAL knote attached by an fd-sharing rfork(RFPROC) child stays linked into the child's p_klist after the child is reaped and its struct proc kfree()d, and the attacker's later close(kq) runs knote_remove()->SLIST_REMOVE() on the freed chunk (panic at knote_remove+0x33, fault 0x18 = offsetof(knote,kn_next), curelm NULL from an M_ZERO-recycled proc). Beyond the panic the primitive is a kernel write into freed 1280-class slab memory (SLIST_REMOVE_HEAD stores elm->kn_next at +496) plus a walk over recycled, attacker-influenceable content. Fix (filt_signal detaches on NOTE_EXIT like filt_proc; filt_sigdetach honors KN_DETACHED) validated by in-guest kernel rebuild: baseline panicked 2/2, patched survived 5/5, EVFILT_SIGNAL still functional.", "exploit_chain": "rfork(RFPROC) child shares fd table (fdshare) -> child registers EVFILT_SIGNAL knote on parent's kqueue (attaches to child's own p_klist, no proc reference) -> child exits+is reaped -> kfree(p, M_PROC) with knote still linked -> slab recycle of the freed proc chunk (fresh M_ZERO proc => p_klist==NULL; p_args argv spray demonstrates user-controlled content landing at offsetof(p_klist)=496 in the same 1280-byte slab class) -> close(kq) -> kqueue drain -> filt_sigdetach -> knote_remove on freed memory -> SLIST_REMOVE writes into / walks freed memory -> kernel page fault. uid0 escalation not completed: would require forging the recycled SLIST so the walk terminates on the (address-unknown) knote, converting the unlink store into an arbitrary-address NULL write (e.g. over ucred cr_uid/cr_ruid); no KASLR on this guest, remaining work is a knote-address leak/deterministic slab layout.", "evidence": [ "run.log / run.2.log - two fresh-boot unprivileged runs, both panic", "panic.txt / panic.2.txt - 'Fatal user address access from kernel mode', Stopped at knote_remove+0x33: movq 0x18(%rdx),%rax", "fix_validation.log - 5/5 SURVIVED on the fixed kernel", "fix_build_excerpt.txt - patched kernel #1 build/install markers", "env.txt - guest kernel geometry (sizeof proc 1208, offsetof p_klist 496, offsetof kn_next 24)", "VERDICT.md - full narrative incl. primitive characterization and fix validation" ], "kernel_refs": [ "sys/kern/kern_sig.c:2667-2679", "sys/kern/kern_sig.c:2681-2687", "sys/kern/kern_sig.c:2696-2705", "sys/kern/kern_exit.c:601", "sys/kern/kern_exit.c:1336", "sys/kern/kern_event.c:382-391", "sys/kern/kern_fork.c:557-558", "sys/kern/kern_descrip.c:2573-2576", "sys/kern/uipc_usrreq.c:1799-1802" ], "poc_changes": "Initial design passed the kqueue fd via fork/SCM_RIGHTS - both are blocked in DragonFly (fdcopy strips kqueue fds; SCM_RIGHTS returns EOPNOTSUPP), discovered as EBADF in the child. Rewritten to rfork(RFPROC) (fd table shared via fdshare), 64 dangling knotes per run, p_args exec-spray for content-controlled recycling (needs kern.ps_arg_cache_limit raised, debug knob only).", "attempts": 11, "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": 5400, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o kqsig_uaf kqsig_uaf.c", "run_cmd": "sysctl kern.ps_arg_cache_limit=8192; su -l t1 -c /tmp/t1home/kqsig_uaf", "code_hash": "b5a849583db05cae43d30db2ef7c677f79d0a11a0268bf379d98781589375c20", "notes": "Panic does not depend on the debug knob (knob only makes recycled content user-controlled for the demonstration). Fix kernel build: make nativekernel+installkernel KERNCONF=X86_64_GENERIC (~35 min in-guest). Guest returned to clean-source snapshot afterwards.", "recommended_fix": "filt_signal(): on NOTE_EXIT detach the knote (PHOLD/knote_remove/KN_DETACHED/p_proc=NULL/PRELE) mirroring filt_proc(); filt_sigdetach(): return early when KN_DETACHED is set", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Sun Aug 30 21:53:26 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Baseline stock kernel panicked on the first run in both attempts; after applying fix.diff and rebuilding/installing the kernel in-guest, the identical PoC survived 5/5 runs and an EVFILT_SIGNAL functional test passed (kevent returned the signal event). Bad behaviour gone.", "fix_evidence": [ "fix_validation.log", "fix_build_excerpt.txt", "fix.diff" ] } |