DragonFlyBSD Kernel Audit
DF-2691 / verdict.json
← back to finding ↓ download raw
{
  "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"
  ]
}