# DF-5141 — `mlx_pci_attach()` double `mlx_free()` on `mlx_attach()` failure

**File:** `sys/dev/raid/mlx/mlx_pci.c:202-206`
**Class:** kernel heap double-free / use-after-free (driver error-path unwind)
**Verdict:** REPRODUCED (kernel panic, allocator double-free detection) — see `verdict.json`
**Fix:** VALIDATED on a patched kernel build — see `fix.diff`, `fix_run.log`

## The bug

`mlx_attach()` (sys/dev/raid/mlx/mlx.c) calls `mlx_free(sc)` internally on **every**
error return (mlx.c:308, 333, 348, 356, 373, 383, 397, 410, 443, 453).
`mlx_free()` is a full teardown — it releases/kfrees the DMA tags, the s/g table,
the interrupt, the IRQ resource, the register window and `mlx_enq2`, and it does
**not** NULL any softc pointers. The PCI glue then calls `mlx_free(sc)` a second
time on the error path:

```c
    error = mlx_attach(sc);
    if (error != 0) {
        mlx_free(sc);        /* <-- mlx_pci.c:204: SECOND full teardown */
        return(error);
    }
```

The second `mlx_free()` re-executes `kfree(sc->mlx_enq2)`,
`bus_dma_tag_destroy(sc->mlx_parent_dmat)` / `mlx_buffer_dmat`,
`bus_dmamem_free(...)`, `bus_teardown_intr(...)`,
`bus_release_resource(... IRQ ...)` and `bus_release_resource(... mlx_mem ...)`
on objects that are already freed — and, in the window between the two calls,
already re-ownable by any other kernel allocation.

## Trigger (hardware-gated)

Any `mlx_attach()` failure after those resources exist: controller
initialisation handshake failure (mlx.c:333), IRQ allocation failure (:348),
`bus_setup_intr` failure (:356), buffer DMA tag failure (:373), s/g mapping
failure (:383/:453), ENQUIRY2 failure (:397), ENQUIRY_OLD failure (:410).
Requires a Mylex DAC960-family PCI controller (0x1069:0x0001/0002/0010 or
0x1011:1065 subdev 0x1069:0x0020) to be present — the QEMU guest has none, so
the entry path cannot fire there; the PoC below drives the *consequence* with
the running kernel's real `mlx_free()`.

## PoC (what is in this directory)

* `df5141.c`   — KLD harness. Builds a softc in exactly the state
  `mlx_attach()` leaves behind on a failing error return (callout_init'd
  timeout, initialised queues, real parent DMA tag created exactly as
  mlx_pci.c:185-192 does, `mlx_enq2` allocated as mlx.c:395 does,
  `mlx_dev` = the real `pci0` device_t), then performs the exact bug
  sequence: `mlx_free()` #1 (= the mlx.c:453 error path), re-allocates the
  freed block the way any concurrent kernel allocation would, and
  `mlx_free()` #2 (= mlx_pci.c:204).
* `df5141_fixed.c` — same harness with the second call removed, i.e. the
  sequence the *patched* `mlx_pci_attach()` performs. Used for fix
  validation.
* `Makefile` — out-of-tree kmod makefile (headers copied next to it).
* `build.sh` / `run.sh` — exact commands.
* `build.log` — module build output (guest, `-Werror` clean).
* `run.log` / `panic.txt` — serial-console capture of the decisive run.
* `env.txt` — guest kernel / PCI state.
* `fix.diff` — one-line caller-side fix (remove the redundant `mlx_free`).
* `fix_build.log` — patched-kernel build (see provenance note inside).
* `fix_run.log` — patched-kernel validation run (clean).

## Result (stock kernel)

```
DF5141: mlx_free #1 (mlx.c:453 path) ... returned (clean teardown)
DF5141: v1=0xfffff800897a5000 owns the freed block: YES
DF5141: mlx_free #2 (mlx_pci.c:204, the bug) ...
panic: memory chunk 0xfffff8008bbd64a0 is already free!
chunk_mark_free() at chunk_mark_free+0xae
_kfree() at _kfree+0x262
bus_dma_tag_destroy() at bus_dma_tag_destroy+0x4b
mlx_free() at mlx_free+0x11b
df5141_modevent() at df5141_modevent+0x252
Debugger("panic")   -> db>
```

* `v1 == enq2-block` proves the freed block is immediately re-allocatable
  between the two `mlx_free()` calls — the second free is a genuine
  use-after-free/double-free of live-owned kernel heap memory, not a benign
  re-free.
* The allocator catches the *first* double-kfree it reaches (the parent DMA
  tag) and panics. In the same sequence, `kfree(mlx_enq2)` (M_DEVBUF, 128
  bytes) is also executed twice — on a system where the tag memory was
  reused between the calls this is silent slab corruption instead of a
  detected panic.

## Result (patched kernel, fix.diff applied)

```
DF5141F: mlx_free #1 (only) ... returned (clean teardown)
DF5141F: v1=0xfffff800897a5000 owns the freed block: YES
DF5141F: (fixed caller) second mlx_free SKIPPED
DF5141F: v2=0xfffff800897a5080 v3=0xfffff800897a5100
DF5141F: RESULT=CLEAN — no double free, no aliasing: patched error path is safe
```

Binary-level confirmation: the stock `/boot/kernel.old` `mlx_pci_attach`
contains **3** `call mlx_free` sites; the patched `mlx_pci.o` contains **2**
(only the two legitimate pre-`mlx_attach` failures at mlx_pci.c:176/:195).

## Reproduce

```
./build.sh        # inside the DragonFly guest (root)
./run.sh          # kldload; watch the serial console / dmesg for DF5141: + panic
```
