# DF-5011 — dimm(4) `dimm_mod_unload()` drains references it does not own → UAF/panic

Kernel module API defect in `sys/dev/misc/dimm/dimm.c`, demonstrated end-to-end on
the QEMU guest: `kldunload dimm` while any KPI user still holds a `dimm_softc`
reference silently **drains the user's reference count and kfrees the live
object**; the user's next call into the dimm KPI (destroy, or an ECC/thermal
sensor update) is a use-after-free into freed heap / unmapped module text —
**proven kernel panic (Fatal trap 12)**.

## Files

* `drainpoc.c`        — PoC kernel module source (mirrors `ecc_e3.c`: uses the
                        dimm KPI without `MODULE_DEPEND`).
* `build.sh`          — exact module build recipe (3 variants + fixed dimm.ko).
* `run.sh`            — baseline reproduction (bug → drain → panic).
* `run-control.sh`    — control: same module WITH `MODULE_DEPEND` (protected).
* `run-fix.sh`        — fix validation with `fix.diff`-patched `dimm.ko`.
* `build.log`         — compiler output (final successful builds).
* `run.log`           — baseline transcript (drain + panic, full serial console).
* `run.2.log`         — control transcript.
* `run.3.log`         — fix-validation transcript.
* `panic.txt`         — kernel panic from the guest serial console.
* `env.txt`           — guest environment.
* `fix.diff`          — git-apply-able fix against `sys/dev/misc/dimm/dimm.c`.

## Precondition (important)

`dimm_mod_unload()` only runs when dimm is a **loadable module**. The stock
`X86_64_GENERIC` kernel compiles dimm in (`device dimm`, sys/config/X86_64_GENERIC:189),
where MOD_UNLOAD never runs. The bug is reachable on kernels built *without*
`device dimm` where `dimm.ko` is pulled in by `memtemp_e5`/`ecc_e5`/`ecc_e3`
modules (this is exactly what `/boot/kernel/dimm.ko` being shipped is for).
For the PoC the guest kernel was rebuilt without `device dimm`:

    # in guest (DragonFly 6.5-DEVELOPMENT, /usr/src present):
    sed -i '' -e '189s/^/#/' /usr/src/sys/config/X86_64_GENERIC   # device dimm
    sed -i '' -e '191s/^/#/' /usr/src/sys/config/X86_64_GENERIC   # device ecc
    sed -i '' -e '193s/^/#/' /usr/src/sys/config/X86_64_GENERIC   # device memtemp
    cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC && \
        make installkernel KERNCONF=X86_64_GENERIC && shutdown -r now

(`ecc`/`memtemp` must go too — they reference `dimm_*` symbols at link time.)

## Build

See `build.sh`. Three PoC module variants are produced from `drainpoc.c` by
`sed` (the module name must be baked in; `DECLARE_MODULE`'s internal `##`
pasting defeats macro-renaming, and `CFLAGS+=` in a kmod Makefile does not
reach the compiler):

* `drainpoc.ko` — module `drainpoc`, NO `MODULE_DEPEND(dimm)`  (bug path)
* `deppoc.ko`   — module `deppoc`, WITH `MODULE_DEPEND(dimm,1,1,1)` (control)
* `pocfix.ko`   — module `pocfix`, NO dep (used against the fixed dimm.ko)

Plus `dimm-fixed.ko` = stock `dimm.c` + `fix.diff`.

## Run (baseline — expect panic)

    # guest, root:
    kldload dimm                       # module-mode dimm (shipped dimm.ko)
    kldload /root/df5011/drainpoc.ko   # creates dimm_softc, holds ref=1
    sysctl hw.dimminfo                 # hw.dimminfo.dimm0.* present
    kldunload dimm                     # BUG: rc=0 — drains+free live softc
    sysctl hw.dimminfo                 # "unknown oid" (object destroyed)
    kldunload drainpoc                 # dimm_destroy on freed object +
                                       # call into unmapped dimm.ko text
                                       # -> Fatal trap 12, system down

## Expected result (all observed, see run.log / panic.txt)

1. `kldunload dimm` returns **0** although a live creator holds the only
   reference (should be refused — the module owns no references of its own).
2. `hw.dimminfo.dimm0.*` disappears: the drain loop ran `dimm_destroy()` on
   the user's softc (kfree at dimm.c:195, sysctl teardown at :193-194).
3. The victim's next KPI call faults:
   `Fatal trap 12: page fault while in kernel mode`,
   `fault virtual address = 0xffffffff826004a0`
   (= dimm.ko text base `0xffffffff82600000` + 0x4a0),
   `supervisor read instruction, page not present` → DDB, system dead.

## Fix validation (run.3.log)

With `fix.diff` applied to `dimm.ko`:
* `kldunload dimm-fixed` with `pocfix` holding a live ref → **"Device busy"**
  (rc=1), console: `dimm0 is still referenced (ref 1); cannot unload`.
* `hw.dimminfo.dimm0.*` still present; victim can destroy its ref properly.
* After the victim's clean destroy, `kldunload dimm-fixed` → rc=0.
* No panic, no drain.
