DF-5011 / verdict.json
{ "finding_id": "DF-5011", "status": "reproduced", "verdict": "REPRODUCED (panic). sys/dev/misc/dimm/dimm.c:305-321 dimm_mod_unload() loops dimm_destroy() until the dimm_softc list is empty; since dimm_destroy() decrements a reference owned by a KPI user (dimm.c:184) and kfrees the softc at zero (dimm.c:195), module unload silently drains FOREIGN references and frees live objects. Demonstrated on the guest with a dep-less KPI user (mirroring in-tree ecc_e3.c, which omits MODULE_DEPEND on dimm): `kldunload dimm` returned 0, tore down hw.dimminfo and kfree()d the victims softc; the victims subsequent dimm_destroy() call faulted on the unmapped dimm.ko text (Fatal trap 12, fault VA = dimm.ko base 0xffffffff82600000 + 0x4a0) and the system died at the DDB prompt. A control module WITH MODULE_DEPEND(dimm,1,1,1) is fully protected (kldunload -> Device busy; auto-load of dimm.ko observed), isolating the defect to dimm.cs own unload path for dep-less users. Root-gated (kldunload) => severity Low; the same primitive is freed-object memory corruption (refcount dec + TAILQ_REMOVE + kfree of a live object) on ECC servers. fix.diff (refuse unload with EBUSY while any softc exists) validated in-guest: drain/free gone, correct lifecycle preserved.", "reproduced": true, "impact": "panic", "confidence": "certain", "exploit_chain": "root loads module-mode dimm.ko (kernel without `device dimm`; stock /boot/kernel/dimm.ko) -> dep-less KPI user (ecc_e3 pattern) calls dimm_create() and holds ref=1 -> root `kldunload dimm` -> linker has no dep ref -> MOD_UNLOAD -> dimm_mod_unload() drains the users ref (1->0), frees sysctl ctx, kfrees softc, unmaps dimm.ko -> victims next KPI call (dimm_destroy at detach, dimm_sensor_ecc_add on ECC interrupt) -> instruction fetch from unmapped module text / write to freed heap -> Fatal trap 12 -> panic, system down. Unprivileged trigger: none (no unprivileged entry point in dimm.c); ceiling is root-triggered panic/heap corruption.", "evidence": "run.log (baseline: kldunload dimm rc=0 with live ref, hw.dimminfo destroyed, then panic), panic.txt (Fatal trap 12, fault VA=0xffffffff826004a0 = dimm.ko+0x4a0, DDB), run.2.log (control with MODULE_DEPEND: Device busy, clean teardown), run.3.log (fix validated: veto + intact object + clean lifecycle), build.log, fix.diff (applies clean to sys/)", "kernel_refs": [ "sys/dev/misc/dimm/dimm.c:305", "sys/dev/misc/dimm/dimm.c:316", "sys/dev/misc/dimm/dimm.c:184", "sys/dev/misc/dimm/dimm.c:195", "sys/dev/misc/ecc/ecc_e3.c:156", "sys/dev/misc/ecc/ecc_e3.c:387", "sys/kern/kern_linker.c:477" ], "poc_changes": "The orchestrator provided no seed PoC; authored from scratch. PoC module builds need: (1) module name baked in via sed (DECLARE_MODULEs internal ## pasting defeats macro renaming; CFLAGS+= in a kmod Makefile does not reach the compiler \u2014 the initial -DWANT_DEP build silently produced a dep-less control, which the first run exposed and which was rebuilt+verified via strings/MDT metadata); (2) compiling dimm.c itself as a kmod requires kernel-generated *_if.h copied from /usr/obj/usr/src/sys/X86_64_GENERIC into the kmod obj dir; (3) the bug path requires a kernel without `device dimm` (stock X86_64_GENERIC compiles dimm in, where MOD_UNLOAD never runs) \u2014 built in-guest with `make nativekernel` after commenting device dimm/ecc/memtemp.", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Fri Sep 25 21:53:46 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (custom kernel w/o device dimm; stock baseline kernel: #0 Thu Jul 2 06:02:54 UTC 2026)", "runtime_sec": 1260, "guest_dirty": 0, "build_cmd": "cd /root/df5011 && sh build.sh (kernel precondition: make -j6 nativekernel KERNCONF=X86_64_GENERIC with device dimm/ecc/memtemp commented, then installkernel + reboot)", "run_cmd": "sh run.sh (baseline, expects panic; run-control.sh and run-fix.sh are the control/fix scripts)", "code_hash": "8252a0fee7a27ff3d41db3f9222c287cbf9dacba6a09578780d032c6719cd75b", "notes": "Also verified live: MODULE_DEPEND fully protects declared dependents (dimm.ko auto-load observed) \u2014 the unprotected population is dep-less users, and in-tree ecc_e3.c is exactly that. Recommended companion fix: add MODULE_DEPEND(ecc_e3, dimm, 1, 1, 1) to sys/dev/misc/ecc/ecc_e3.c. dimm.c itself has no unprivileged attack surface (sysctl writes gated by SYSCAP_NOSYSCTL_WR, kern_sysctl.c:1446). Guest reset to clean `with-src` snapshot after the run (baseline panic + custom kernel reverted).", "recommended_fix": "dimm_mod_unload(): return EBUSY when any dimm_softc still exists (veto the module unload) instead of looping dimm_destroy() until the list is empty; propagate the error from MOD_UNLOAD (see fix.diff); optionally add the missing MODULE_DEPEND(ecc_e3, dimm, 1,1,1).", "fix_status": "fixed", "fix_baseline_reproduced": true, "fix_patched_reproduced": false, "fix_verdict": "fix.diff (dimm_mod_unload -> int, EBUSY veto while list non-empty, error propagated from MOD_UNLOAD; forward prototype at dimm.c:75 updated too) built as dimm-fixed.ko in-guest and validated: with a dep-less user holding ref=1, `kldunload dimm-fixed` fails with Device busy and logs dimm0 is still referenced (ref 1); cannot unload; the softc and its sysctl tree survive; the user then destroys its own reference cleanly and the module unloads rc=0 afterwards. Baseline behavior (silent drain + kfree + victim panic) is gone. fix_patched_reproduced=false means the BUG does NOT reproduce on the patched module (the fix works), per the schema semantics.", "fix_evidence": "run.3.log (full fixed-module transcript), build.log (dimm-fixed.ko build), fix.diff (git apply --check passes against sys/)" } |