DF-2232 / verdict.json
{ "finding_id": "DF-2232", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "Reproduced on the stock DragonFly 6.5 X86_64_GENERIC kernel through the real exported KPI. (b) idr_alloc(start=1<<29) drives idr_grow to nf=2^30-1 and the live allocator-boundary hook captured kmalloc(17179869168 bytes, M_ZERO|M_WAITOK) which never returned on the 4 GiB guest (vm_wait wedge in kmem_slab_alloc); the hard-IRQ watchdog panicked at 15s with the captured size. (c) idr_get_new_above(sid=INT_MAX) poisons idr_maxwant, then idr_pre_get spins forever in the growth loop (2*INT_MAX+1 wraps to -1, -1 <= INT_MAX) holding the lwkt idr token while a second thread livelocks in idr_remove on the same idr; reproduced both on the verbatim-compiled KLD copy and on the kernel shipped binary. No memory-unsafety path exists (wrapped nf cannot escape the loop to any size computation, and any escape would over- not under-allocate), so the impact ceiling is kernel hang / OOM draw / livelock - DoS, no privesc. fix.diff (bound want at IDR_MAX_IDS, propagate ENOMEM) validated on a rebuilt kernel: control output bit-identical, boundary wants fail cleanly with -ENOMEM, no giant kmalloc reaches the allocator, no spin, no livelock, zero panics.", "exploit_chain": "", "evidence": "run.b.log + panic.b.txt line kmalloc(17179869168 bytes [0x3fffffff0]) at the allocator boundary followed by watchdog panic DF2232-WD; panic.c1.txt / panic.c1b.txt state a_stage=2 a_done=0 b_started=1 b_acquired=0 (infinite spin, token livelock); panic.c2.txt same on the shipped binary with last_kmalloc_req=0; fix_run.log shows idr_alloc RETURNED -12, pre_get RETURNED 0, a_done=1 b_acquired=1, zero panics on the fixed kernel; fix/control-fixed.log shows control PASSED unchanged", "kernel_refs": [ "sys/libkern/linux_idr.c:427", "sys/libkern/linux_idr.c:429", "sys/libkern/linux_idr.c:430", "sys/libkern/linux_idr.c:437", "sys/libkern/linux_idr.c:274", "sys/libkern/linux_idr.c:327", "sys/libkern/linux_idr.c:365", "sys/kern/kern_slaballoc.c:863", "sys/kern/kern_slaballoc.c:1706", "sys/kern/kern_slaballoc.c:1776" ], "poc_changes": "Built the rig from scratch (no seed existed): verbatim #include of linux_idr.c with exported-symbol rename guard and sha256 gate; kmalloc observation wrapper outside the verbatim file; systimer watchdog armed on a remote CPU firing from hard-IRQ context so every phase terminates with decisive output; second kernel thread as token-livelock probe; fixed rig defects found during runs (expectation arithmetic 2047 vs 1023, EPERM on nonzero sysctl return, unload-time systimer race -> module stays resident after crash-phases)", "attempts": 7, "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 (baseline) / #1: Sat Oct 17 10:36:44 UTC 2026 (fixed kernel)", "runtime_sec": 2340, "guest_dirty": 0, "build_cmd": "cd findings/poc/DF-2232 && ./build.sh (sha256 gate + make on guest; fixed pass: DF2232_IDR_SHA=1c04da32d604f7b5164832be1da227b24980c0804665facdf20fe0fa263275e3 ./build.sh)", "run_cmd": "kldload /root/df2232/df2232.ko; sysctl debug.df2232=<1|2|3|4|5> (1,2 control; 3 giant kmalloc; 4 spin verbatim; 5 spin kernel KPI) or ./run.sh a|b|c1|c2", "code_hash": "230d2ad65c9ccea17eea3893260a19989e3ad83045eab556edce7daf13c896ee", "notes": "Team directive assessed: no under-allocation path - the wrapped nf=-1 cannot exit the loop (escape needs want < -1; all reachable wants are non-negative: idr_alloc rejects start<0 at linux_idr.c:365, idr_maxwant only rises via max() at :327-328), and even a hypothetical escape promotes nf to size_t producing 0xFFFFFFFFFFFFFFF0, an over-allocation the allocator refuses; bcopy uses the old idr_count and the :441 race guard frees on non-growth. Pure DoS (hang/OOM/livelock); no uid=0 chain possible. Latent KPI hazard: no in-tree caller forwards attacker-controlled start/sid today (drm_gem start=1, amdgpu 16-bit PASID). M_IDR type limit = RAM/10 does not stop a single first giant request (kern_slaballoc.c:863 checks cumulative use). One extra serial-console artifact: the first fixed-kernel run crashed during kldunload AFTER all phases passed - rig defect (systimer teardown race), corrected; second run clean end-to-end.", "recommended_fix": "Bound growth in idr_grow: return ENOMEM when want >= IDR_MAX_IDS ((1<<24)-1), make idr_grow static int, propagate to idr_pre_get (result 0 failure) and idr_alloc (-ENOMEM) - see fix.diff", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Sat Oct 17 10:36:44 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Single-fix kernel (patch -p1, make nativekernel, 320s, -Werror clean, installkernel, reboot): baseline manifestations gone - phase 3 returns -ENOMEM with the largest allocator request observed at 4080 bytes (idr_init), phases 4/5 show pre_get RETURNED 0 and a_done=1 b_acquired=1 (no spin, no livelock) on both the fixed verbatim copy and the fixed kernel functions; control phases bit-identical to baseline; zero panics; guest stayed up.", "fix_evidence": "fix_run.log (fixed-kernel serial console), fix/control-fixed.log (control PASSED), fix_build.log (kernel build), build.log (rig rebuilt against the fixed file, sha 1c04da32d604f7b5164832be1da227b24980c0804665facdf20fe0fa263275e3)" } |