DF-2799 / verdict.json
{ "finding_id": "DF-2799", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "REPRODUCED: permanent kernel-wide AB-BA deadlock between the global jail_lock and the per-CPU gd_sysctllock locks. Root side: sys_jail holds jail_lock (kern_jail.c:275) across prison_sysctl_create (:223) whose SYSCTL_ADD_* macros take the all-CPU sysctl xlock (_sysctl_xlock, kern_sysctl.c:1641-51). Unprivileged side: userland_sysctl holds its CPU's gd_sysctllock SHARED across the whole handler dispatch (kern_sysctl.c:1571-73) and sysctl_jail_list blocks acquiring jail_lock (kern_jail.c:696) inside it. On the stock kernel, 4 unprivileged jail.list readers plus a 3000-iteration jail(2) churn wedged the guest in seconds: markers listonce/jailonce returned rc 124 under timeout, even `echo` stopped returning (new process startup blocks on the exclusively-held per-CPU locks via rtld boot sysctls), and the serial console flooded 'maxproc limit exceeded by jailchurn' because the deadlocked parent never reaps. Zero id-sharing flood lines at 3000 iterations, isolating this wedge from DF-2802. On the patched kernel (prison_sysctl_create moved before publication and out of jail_lock; sys_jail drops the lock before kern_jail) the identical storm completed: 1,146,880 jail.list reads with 0 errors, markers rc=0, guest healthy. Permanent unrecoverable hang requiring reboot; unprivileged-triggerable whenever jail(2) runs on the host.", "exploit_chain": "unprivileged user: while(1) sysctlbyname(\"jail.list\") โ each read parks holding a per-CPU gd_sysctllock; concurrent root jail(2) reaches SYSCTL_XLOCK under jail_lock; cycle closes; whole-system wedge (all jails, all sysctl activity, new process exec). DoS ceiling; no memory-safety primitive.", "evidence": [ "findings/poc/DF-2799/run.log โ attempt-2 evidence: PRE markers rc=0, post-storm PROBE/TRIVIAL/MARKERS all 124 with no output, maxproc console flood, flood-lines=0 note", "findings/poc/DF-2799/run.patched.log โ fixed kernel: storm survives, POST-LISTONCE:0 (335 bytes listed mid-storm), 1.15M reads 0 errors", "findings/poc/DF-2799/serial_wrong_oid_run.log โ attempt-1 context (wrong oid; wedge that run was DF-2802-family)" ], "kernel_refs": [ "sys/kern/kern_jail.c:275", "sys/kern/kern_jail.c:223", "sys/kern/kern_jail.c:235", "sys/kern/kern_jail.c:696", "sys/kern/kern_sysctl.c:442", "sys/kern/kern_sysctl.c:1480", "sys/kern/kern_sysctl.c:1571", "sys/kern/kern_sysctl.c:1641", "sys/sys/sysctl.h:192-194" ], "poc_changes": "Reader oid corrected from kern.jail.list to jail.list (the SYSCTL_NODE parent is the root, not kern โ attempt 1 used the wrong oid and its readers no-op'd); run_user reader launch fixed (cd must precede each nohup; 'cd X && nohup A &' backgrounds the compound and later nohups run in the wrong cwd); run.sh rewritten as a host-side vm.sh driver with every guest command timeout-wrapped because the guest wedges by design.", "attempts": 2, "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": 900, "guest_dirty": 0, "build_cmd": "cc -O2 -o jailchurn jailchurn.c && cc -O2 -o jailistreader jailistreader.c && cc -O2 -o listonce listonce.c && cc -O2 -o jailonce jailonce.c", "run_cmd": "sh run.sh (host-side driver: vm.sh run_user readers on jail.list + vm.sh run_root './jailchurn 3000', then timeout-wrapped markers)", "code_hash": "a095ededefb7fc56036245ec659006abc9d74f94fc17fc26f6f28fe4807e3a0c", "notes": "Lock-cycle proof is structural (lockmgr sleeps never time out; no cycle detection). The 3000-iteration bound proves purity: zero 'can't re-use a leaf' lines means no DF-2802 sharing was involved, so the wedge is solely the lock cycle. Fixed kernel = #1 Tue Sep 1 13:39:40 UTC 2026.", "recommended_fix": "Never call sysctl add/remove while holding jail_lock: create the prison sysctl tree before publishing the prison (and outside jail_lock) in kern_jail(), and release sys_jail's outer jail_lock before calling kern_jail(); prison_free already drops the lock before prison_sysctl_done.", "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": "Same storm on the patched kernel ran to completion: 4 readers performed 1,146,880 jail.list reads with 0 errors while the 3000-jail churn ran; listonce returned live data (rc=0) DURING the storm; jailonce rc=0; guest fully responsive after. Baseline wedge (markers 124, echo dead, maxproc flood) is gone.", "fix_evidence": "findings/poc/DF-2799/run.patched.log; fix.diff (hunks: kern_jail.c kern_jail/sys_jail restructure)" } |