DF-5141 / verdict.json
{ "finding_id": "DF-5141", "status": "reproduced", "reproduced": true, "impact": "panic", "confidence": "certain", "verdict": "mlx_pci_attach() calls mlx_free(sc) a second time on the mlx_attach()-failure path (mlx_pci.c:204) even though mlx_attach() already performs a complete teardown on every one of its 10 error exits (mlx.c:308-453); mlx_free() NULLs no pointers, so the second call re-executes kfree(mlx_enq2), double bus_dma_tag_destroy, double bus_dmamem_free, double IRQ/register-window release on freed, re-ownable objects. Proven on the guest with the running kernels real mlx_free(): the exact two-call sequence panics with memory chunk ... is already free! (stack: chunk_mark_free <- _kfree <- bus_dma_tag_destroy <- mlx_free), and an ordinary kmalloc between the two frees took ownership of the mlx_enq2 block (v1 == enq2 pointer), demonstrating a true double-free/UAF of live-owned kernel heap rather than a benign re-free. Entry path is hardware-gated (needs a Mylex DAC960 PCI controller plus an attach failure: IRQ alloc/setup failure, DMA tag failure, s/g map failure, ENQUIRY2/ENQUIRY_OLD failure, firmware handshake error) \u2014 no unprivileged trigger exists, so uid=0 escalation is not applicable; realistic ceiling on affected hardware is boot-time kernel heap corruption/panic.", "exploit_chain": "n/a for user-driven exploitation (trigger is driver-internal error handling at boot/module-load on Mylex hardware, not attacker-controlled input). Demonstrated primitive chain: mlx_attach() error -> mlx_free() #1 (clean teardown; freed chunks return to the live allocator) -> [any kernel allocation may take ownership, shown: kmalloc(128,M_DEVBUF) returned the just-freed mlx_enq2 block] -> mlx_pci.c:204 mlx_free() #2 -> kfree/bus_dma_tag_destroy on already-freed/re-owned memory -> allocator double-free detection panic (or, if the chunks were reused, silent slab corruption).", "evidence": "run.log/panic.txt: panic text + stack through the kernels mlx_free; the v1=0xfffff800897a5000 owns the freed block: YES line proves re-ownership between the frees (enq2=0xfffff800897a5000 from the first DF5141 line). fix_run.log: patched-kernel run of df5141f.ko (fixed single-free sequence) completes and unloads cleanly. fix_build.log: patched mlx_pci.o has 2 mlx_free call sites vs 3 in stock /boot/kernel.old mlx_pci_attach. build.log: -Werror-clean module build. env.txt: guest kernels (stock #0 Jul 2 / patched #1 Sep 30), no Mylex PCI device on guest, source md5 sync.", "kernel_refs": [ "sys/dev/raid/mlx/mlx_pci.c:202-206", "sys/dev/raid/mlx/mlx.c:149-193", "sys/dev/raid/mlx/mlx.c:308", "sys/dev/raid/mlx/mlx.c:333", "sys/dev/raid/mlx/mlx.c:348", "sys/dev/raid/mlx/mlx.c:356", "sys/dev/raid/mlx/mlx.c:373", "sys/dev/raid/mlx/mlx.c:383", "sys/dev/raid/mlx/mlx.c:397", "sys/dev/raid/mlx/mlx.c:410", "sys/dev/raid/mlx/mlx.c:443", "sys/dev/raid/mlx/mlx.c:453" ], "poc_changes": "No seed existed (orchestrator reserved fresh IDs). Harness authored from scratch: KLD module calling the kernels real mlx_free(); had to (1) copy mlx_compat/io/var/reg headers next to the module because CFLAGS include paths are rebuilt by bsd.kmod.mk, (2) add generated device_if.h/bus_if.h/pci_if.h to SRCS, (3) include sys/disk.h+sys/devicestat.h for mlxvar.hs mlxd_softc, (4) drop MODULE_DEPEND(df5141,mlx,...) \u2014 in-kernel mlx registers as pci/mlx so the KLD linker refused the dependency, (5) use a real pci0 device_t for mlx_dev, (6) transfer via openssl base64 through the ssh-stdin shell (guest /bin/sh truncates >1KB lines).", "attempts": 3, "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", "runtime_sec": 120, "guest_dirty": 1, "build_cmd": "cd /tmp/df5141 && make SYSDIR=/usr/src/sys (KLD df5141.ko, -Werror clean)", "run_cmd": "kldload /tmp/df5141/df5141.ko", "code_hash": "8c7548721900562dc3c5049bed0431160d155ec6b73084f9e5ec3cc21dbcfe06", "notes": "Guest left in ddb by the decisive run and later reset with vm.sh reset with-src (final state: clean-source snapshot, guest up). Fix validated on a rebuilt kernel (see fix fields). Severity Low per rubric (requires specific hardware + attach failure; no unprivileged trigger) but memcorrupt bucket with a proven double-free primitive. Binary-level fix proof: stock mlx_pci_attach=3 mlx_free calls, patched=2.", "recommended_fix": "Remove the redundant mlx_free(sc) at sys/dev/raid/mlx/mlx_pci.c:204 (mlx_attach() already tears down fully on every error return).", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Wed Sep 30 07:26:18 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_verdict": "fix.diff (one line: delete mlx_free(sc) from the mlx_attach()-failure branch) applied to the guests pristine /usr/src, kernel rebuilt with make -j6 nativekernel (RC=0, -Werror clean), installed and booted (kernel #1 Sep 30 07:26:18). Baseline (stock #0): bug-sequence run panics with double-free detection. Patched: the fixed-caller sequence (df5141f.ko, identical harness minus the removed second call) runs clean \u2014 no panic, no aliasing, module loads AND unloads, guest stays up. Caller-side fix means the harnesss emulation of the caller is updated to match the patched caller; corroborated objectively: patched mlx_pci.o contains 2 mlx_free call relocations vs 3 in the stock kernels mlx_pci_attach, so the shipped driver can no longer perform the double teardown.", "fix_evidence": "fix.diff; fix_run.log (DF5141F RESULT=CLEAN lines); fix_build.log (build RC=0, kernel #1 artifacts, objdump call counts 3->2); fix_poc_build.log (df5141f.ko build); env.txt (both unames)." } |