DF-2801 / verdict.json
{ "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 โ 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 โ rated Low severity, impact ceiling DoS.", "exploit_chain": "host root: loop { jail(path=<regular file>) } โ 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 โ decisive stock run: F0 6476 / control -104 / leak +1538, ENOTDIR=2000, REPRODUCED line", "findings/poc/DF-2801/run.patched.log โ 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 โ first version parsed it as a string and read 0; (2) baseline must be compared as settled floors with reclaim pressure between phases โ 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) โ 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_jail's 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 โ 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)" } |