⬢ DragonFlyBSD Kernel Audit
DF-3224 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-3224",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "dos",
  "confidence": "certain",
  "verdict": "REPRODUCED on DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC, INVARIANTS), two independent boots. procfs_doctls signal branch (procfs_ctl.c:333) calls ksignal() with no authorization: kill(2)s CANSIGNAL gate (p_trespass + reaper_sigtest + pid-1 NOREBOOT cap, kern_sig.c:803/signal2.h:46) is entirely skipped; the only gate is the open-time vnode check (mode 0200, owner=target current euid), never revalidated. Demonstrated deltas as uid 1001: (1) attacker inside a PROC_REAP_ACQUIRE subreaper subtree gets EPERM from kill(2) but delivers SIGTERM via ctl write (victim handler ran, exit 57); (2) ctl fd opened pre-exec, victim execs setuid-root helper + setresuid(0,0,0): kill(2) EPERM yet the stale ctl fd SIGKILLs the now-root process. Any of 32 signals to any process whose euid matched at fd-open, regardless of kernel signal ACLs. Fix validated on a patched kernel (make nativekernel, reboot): attacker ctl write fails EPERM while kill(2) semantics unchanged (the outside-subtree hup from main still delivers, as kill(2) would); root-dropped victim survives the stale-fd write.",
  "exploit_chain": "unpriv uid U attacker: (a) procctl(PROC_REAP_ACQUIRE) supervisor forks attacker inside subtree; kill(2) to same-uid outside-subtree victim = EPERM by design; open /proc/victim/ctl (0200 owner=victim-euid=U) and write term/kill -> ksignal bypasses reaper_sigtest -> signal delivered. (b) open victims ctl while euid matches, victim execs setuid-root and full-drops ruid (su/login-style), kill(2)=EPERM, stale fd write kill -> root process dies.",
  "evidence": "run.log lines: [attacker] kill(...SIGTERM)... REFUSED: Operation not permitted + [attacker] write \"term\" to /proc/.../ctl... DELIVERED + VICTIM pid CAUGHT SIGNAL 15 + part2 kill(...SIGKILL) now... REFUSED + root-dropped victim KILLED by signal 9 via /proc ctl. run.2.log = identical after clean-snapshot reset. run.fixed.log = patched kernel: ctl write FAILED, victim survives.",
  "kernel_refs": [
    "sys/vfs/procfs/procfs_ctl.c:319",
    "sys/vfs/procfs/procfs_ctl.c:333",
    "sys/kern/kern_sig.c:803",
    "sys/kern/kern_sig.c:786",
    "sys/sys/signal2.h:46",
    "sys/kern/kern_prot.c:1023",
    "sys/kern/kern_fork.c:1285",
    "sys/vfs/procfs/procfs_subr.c:216",
    "sys/vfs/procfs/procfs_vnops.c:584",
    "sys/vfs/procfs/procfs_vnops.c:607",
    "sys/kern/kern_sig.c:1118"
  ],
  "poc_changes": "Seed concept kept; PoC written fresh. Part1 uses PROC_REAP_ACQUIRE (syscall, unprivileged, kern_fork.c:986) to construct the reaper-subtree differential. Part2 uses a root-installed setuid-root dropdemo (setresuid(0,0,0)) modeling su/login-style credential sanitization; victim must exec the ROOT-OWNED copy (a cp by the unpriv user drops the setuid bit). Added err.h include.",
  "attempts": 3,
  "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": 420,
  "guest_dirty": 0,
  "build_cmd": "cc -O -o df3224 df3224.c ; (root, once) cc -O -o dropdemo dropdemo.c && chown root:wheel dropdemo && chmod 4555 dropdemo",
  "run_cmd": "./df3224 /full/path/to/dropdemo",
  "code_hash": "4ffa037c2d0e578fc3e849ad31f98f1e374a6499d8c0ca31b95ee4a95e05ea7d",
  "notes": "Impact dos is the honest mapping: demonstrated ceiling is unauthorized SIGKILL/SIGSTOP/SIGCONT against processes the kernels signal ACL protects (incl. privileged processes after credential sanitization); no memory corruption, no uid0 route. Also covers pid-1 NOREBOOT-restricted-root signaling (open requires euid 0, so unpriv-only scenarios are the two demonstrated). Third unpriv delta note: P_WEXIT processes are signaled through ctl where kern_kill skips (cosmetic). Fix validated in the same kernel as DF-3225/DF-3226 fixes (single nativekernel build).",
  "recommended_fix": "Gate the ksignal branch with CANSIGNAL(p, nm->nm_val, 1), exactly mirroring kern_kill().",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Tue Sep  8 20:08:46 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 #0 Jul 2 kernel): both bypass deltas reproduce. Patched (nativekernel with fix.diff, #1 Sep 8): attacker ctl signal writes fail EPERM (run.fixed.log/run.fixed2 outputs FAILED), outside-subtree sender still delivers as kill(2) would, root-dropped victim survives the stale-fd SIGKILL write. Guest reverted to clean with-src snapshot afterwards.",
  "fix_evidence": "findings/poc/DF-3224/run.fixed.log; findings/poc/DF-3225/fixbuild.log (build); session transcript uname #1: Tue Sep 8 20:08:46 UTC 2026"
}