⬢ DragonFlyBSD Kernel Audit
DF-2801 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2801",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "dos",
  "confidence": "certain",
  "verdict": "REPRODUCED: failed jail(2) calls permanently pin namecache entries. kern_jail() cache_copy()s the jail path handle into pr->pr_root (kern_jail.c:206) and no error path after that drops it (assign_prison_id :211-216, prison_sysctl_create, kern_jail_attach/out2 :227-244; sys_jail kfrees pr at :342-351 with the reference live). Trigger: jail path = regular file \u2014 nlookup succeeds, cache_copy runs, kern_chroot/checkvp_chdir fails ENOTDIR (vfs_syscalls.c:2058). Measured with a floor-comparison design (2000 files per cycle, 8000-file pressure churn + settle between phases to defeat lazy hammer2 reclaim): stock kernel control cycle -104 vs leak cycle +1538 permanently-pinned entries for 2000 calls (all 2000 failing ENOTDIR, i.e. exactly the traced path). Patched kernel (cache_drop on every error path, NULL-guarded): leak growth collapses to +118, same order as control noise, with the identical ENOTDIR failure count. Root-gated (jail(2) needs SYSCAP_NOJAIL_CREATE) so this is a privileged-user unbounded-kernel-memory-exhaustion primitive, not an unpriv escalation \u2014 rated Low severity, impact ceiling DoS.",
  "exploit_chain": "host root: loop { jail(path=<regular file>) } \u2014 each failing call leaks one namecache reference forever; unbounded kernel memory growth. No unprivileged route (jail(2) is capability-gated and denied in jails).",
  "evidence": [
    "findings/poc/DF-2801/run.3.log \u2014 decisive stock run: F0 6476 / control -104 / leak +1538, ENOTDIR=2000, REPRODUCED line",
    "findings/poc/DF-2801/run.patched.log \u2014 fixed: control -91, leak +118 (noise), ENOTDIR=2000 same path"
  ],
  "kernel_refs": [
    "sys/kern/kern_jail.c:206",
    "sys/kern/kern_jail.c:211",
    "sys/kern/kern_jail.c:223",
    "sys/kern/kern_jail.c:227",
    "sys/kern/kern_jail.c:234",
    "sys/kern/kern_jail.c:342",
    "sys/kern/vfs_syscalls.c:2058",
    "sys/kern/vfs_cache.c:751"
  ],
  "poc_changes": "two measurement fixes: (1) vfs.cache.numcache is an INT sysctl \u2014 first version parsed it as a string and read 0; (2) baseline must be compared as settled floors with reclaim pressure between phases \u2014 instantaneous deltas are confounded by lazy hammer2 reclaim (first design took the baseline after file creation and showed delta 0).",
  "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 -O2 -o jailleak jailleak.c",
  "run_cmd": "sh run.sh   (root; leak cycle + control cycle with pressure/settle floors)",
  "code_hash": "ffe400b143d020207ae8a3630d69a207b828b5bcb1e5dbe7889125dae0090b94",
  "notes": "cache_copy also runs on the SUCCESS path where the reference is correctly dropped by prison_free (:837) \u2014 only the error paths leak. Fixed kernel = #1 Tue Sep 1 13:39:40 UTC 2026.",
  "recommended_fix": "Add NULL-guarded cache_drop(&pr->pr_root) to every kern_jail error return after cache_copy and to sys_jails out path before kfree(pr).",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Tue Sep  1 13:39:40 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: identical trigger (2000 ENOTDIR failures) pins nothing \u2014 leak-cycle floor growth +118 vs control -91 (noise), against +1538 on stock. Permanent pinning eliminated.",
  "fix_evidence": "findings/poc/DF-2801/run.patched.log; fix.diff (kern_jail.c cache_drop hunks)"
}