โฌข DragonFlyBSD Kernel Audit
DF-5141 / df5141_fixed.c
โ† back to finding โ†“ download raw
/*
 * DF-5141 โ€” mlx_pci.c:204 double mlx_free() on mlx_attach() failure.
 *
 * The QEMU guest has no Mylex PCI RAID controller (no 0x1069 / 0x1011:1065
 * device), so mlx_pci_probe() never matches and mlx_pci_attach() cannot be
 * entered the hardware way.  This KLD reproduces the *consequence* of the
 * bug using the RUNNING KERNEL'S REAL mlx_free():
 *
 *   1. Fabricate a softc in exactly the state mlx_attach() leaves it in on
 *      an error return (e.g. the mlx.c:453 "final s/g mapping" failure):
 *        - callout initialized               (mlx.c:275)
 *        - a real parent DMA tag created      (mlx_pci.c:185-192)
 *        - mlx_enq2 allocated                 (mlx.c:395)
 *        - queues initialized                 (mlx.c:280-282)
 *        - mlx_dev = a real PCI device_t      (mlx_pci.c:131)
 *   2. Call mlx_free(sc)  โ€” this is what mlx_attach() does internally on
 *      every error return (mlx.c:308,333,348,356,373,383,397,410,443,453).
 *   3. Re-allocate the freed block the way any same-size kernel allocation
 *      would (this is what ANY concurrent kmalloc on the system does).
 *   4. Call mlx_free(sc) AGAIN โ€” this is mlx_pci.c:204, the bug:
 *      mlx_free() does not NULL any pointers, so the second call
 *      re-executes kfree(mlx_enq2), bus_dma_tag_destroy(parent_dmat), ...
 *      on objects that are already free / re-owned by other code.
 *   5. Allocate twice more and compare pointers: if the block freed by the
 *      second mlx_free() comes back while our earlier allocation still owns
 *      it, the kernel heap now has one block live twice โ€” the classic
 *      double-free corruption primitive, proven with the kernel's own code.
 *
 * The only difference from the real path is *which* PCI device_t sits in
 * sc->mlx_dev (mlx_free only uses it for device_get_unit() inside
 * dev_ops_remove_minor(), a no-op when no mlx cdevs exist) and that the
 * hardware-dependent s/g table / IRQ setup (which needs the card) are left
 * NULL โ€” exactly as they are on several of the reachable error paths
 * (e.g. mlx.c:333, 348, 356, 373, 383, 397).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include <sys/conf.h>
#include <sys/devicestat.h>
#include <sys/disk.h>

#include "mlx_compat.h"
#include "mlxio.h"
#include "mlxvar.h"
#include "mlxreg.h"

static void *df_v1, *df_v2, *df_v3;

static int
df5141f_modevent(module_t mod, int type, void *arg)
{
	struct mlx_softc *sc;
	devclass_t dc;
	void *p;
	size_t sz = sizeof(struct mlx_enquiry2);

	switch (type) {
	case MOD_LOAD:
		dc = devclass_find("pci");
		if (dc == NULL) {
			kprintf("DF5141F: no pci devclass?!\n");
			return (ENODEV);
		}

		sc = kmalloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO);
		if (sc == NULL)
			return (ENOMEM);

		/* --- state exactly as mlx_attach()/mlx_pci_attach() build it --- */
		callout_init(&sc->mlx_timeout);				/* mlx.c:275 */
		TAILQ_INIT(&sc->mlx_work);				/* mlx.c:280 */
		TAILQ_INIT(&sc->mlx_freecmds);				/* mlx.c:281 */
		sc->mlx_dev = devclass_get_device(dc, 0);		/* pci0 */
		if (sc->mlx_dev == NULL) {
			kfree(sc, M_DEVBUF);
			kprintf("DF5141F: no pci0 device_t\n");
			return (ENODEV);
		}
		sc->mlx_iftype = MLX_IFTYPE_5;				/* dispatched at mlx.c:301 */
		sc->mlx_enq2 = kmalloc(sz, M_DEVBUF, M_WAITOK | M_ZERO); /* mlx.c:395 */
		if (sc->mlx_enq2 == NULL) {
			kfree(sc, M_DEVBUF);
			return (ENOMEM);
		}
		/* parent DMA tag exactly as mlx_pci.c:185-192 creates it */
		if (bus_dma_tag_create(NULL, 1, 0,
		    BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR,
		    MAXBSIZE, MLX_NSEG, BUS_SPACE_MAXSIZE_32BIT,
		    BUS_DMA_ALLOCNOW, &sc->mlx_parent_dmat) != 0) {
			kfree(sc->mlx_enq2, M_DEVBUF);
			kfree(sc, M_DEVBUF);
			return (ENOMEM);
		}
		p = sc->mlx_enq2;
		kprintf("DF5141F: softc=%p enq2=%p (sz=%zu) dmat=%p\n",
		    sc, p, sz, sc->mlx_parent_dmat);

		/* ---- first mlx_free(): mlx_attach() error path (mlx.c:453) ---- */
		kprintf("DF5141F: mlx_free #1 (only) (mlx.c:453 path) ...\n");
		mlx_free(sc);
		kprintf("DF5141F: mlx_free #1 (only) returned (clean teardown, as designed)\n");

		/* any other code on the system may now take the freed block */
		df_v1 = kmalloc(sz, M_DEVBUF, M_WAITOK | M_ZERO);
		kprintf("DF5141F: v1=%p owns the freed block: %s\n",
		    df_v1, df_v1 == p ? "YES" : "no");

		/* ---- PATCHED mlx_pci.c: NO second mlx_free() ----
		 * (fix.diff removes mlx_pci.c:204; mlx_attach() already
		 * performed the single, clean teardown above) */
		kprintf("DF5141F: (fixed caller) second mlx_free SKIPPED\n");
		df_v2 = kmalloc(sz, M_DEVBUF, M_WAITOK | M_ZERO);
		df_v3 = kmalloc(sz, M_DEVBUF, M_WAITOK | M_ZERO);
		kprintf("DF5141F: v2=%p v3=%p\n", df_v2, df_v3);
		if (df_v2 == df_v1 || df_v3 == df_v1 || df_v2 == p || df_v3 == p) {
			kprintf("DF5141F: RESULT=UNEXPECTED ALIASING (bug-like)\n");
		} else {
			kprintf("DF5141F: RESULT=CLEAN โ€” no double free, "
			    "no aliasing: patched error path is safe\n");
		}
		/* v1/v2/v3 and sc are deliberately leaked: freeing them could
		 * compound the corruption under test */
		return (0);
	case MOD_UNLOAD:
		kprintf("DF5141F: unload (test objects intentionally leaked)\n");
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t df5141f_mod = {
	"df5141f",
	df5141f_modevent,
	NULL
};
DECLARE_MODULE(df5141, df5141f_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);