⬢ DragonFlyBSD Kernel Audit
DF-2232 / verdict.json
← back to finding ↓ download raw
{
  "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)"
}