DF-4614 / doublefree_harness.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 149 150 151 152 153 154 155 | /* * DF-4614 harness: control-flow proof of the ips command-queue double-free. * * This mirrors, statement for statement, the DRAMAS-relevant control flow of * sys/dev/raid/ips/ips.c: * * ips_adapter_init() [ips.c:374-464] * sc->max_cmds = 1; [ips.c:413] * ips_cmdqueue_init(sc); [ips.c:414] <-- RETURN IGNORED * (bus_dmamem_alloc fails) [ips.c:164-167 -> goto error] * ips_cmdqueue_free(sc); [ips.c:190] * kfree(sc->commandarray) [ips.c:140] <-- #1, pointer NOT nulled * return ENOMEM (discarded by caller) * ips_adapter_reinit(sc, 0) [ips.c:416] (card works; succeeds) * ips_ffdc_reset(sc) [ips.c:421] -> ips_get_free_cmd sees * IPS_OFFLINE -> EIO * -> goto error [ips.c:424] * error: * ips_adapter_free(sc) [ips.c:462] * ips_cmdqueue_free(sc) [ips.c:533] * reads sc->commandarray[0].command_phys_addr [ips.c:125] <-- UAF read * kfree(sc->commandarray) [ips.c:140] <-- #2 DOUBLE FREE * * The bus_dma* calls are stubbed; bus_dmamem_alloc() is forced to fail the way * it does under DMA-map exhaustion. The allocator aborts on double free so the * demonstration is unambiguous. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> /* ---------- stubs of DragonFly primitives ---------- */ static int kfree_calls; static void *last_freed; static void *kmalloc_(size_t n) { void *p = calloc(1, n); if (!p) abort(); return p; } static void kfree_(void *p) { kfree_calls++; if (p == last_freed) { printf("[!] DOUBLE FREE: kfree(#%d) on %p already freed at kfree(#%d)\n", kfree_calls, p, kfree_calls - 1); exit(42); /* allocator would corrupt its free lists here */ } last_freed = p; free(p); } static int bus_dmamem_alloc_fail; /* flip to force the failure mode */ /* ---------- mirrored driver types (ips.h) ---------- */ struct ips_command { uint8_t command_number, id, timeout; void *sc; void *command_dmamap; void *command_buffer; uint32_t command_phys_addr; void *data_dmatag, *data_dmamap; uint32_t status; struct ips_command *next; void *data_buffer, *arg, *callback; int completed; }; struct ips_softc { int state; int max_cmds; int used_commands; struct ips_command *commandarray; struct ips_command *staticcmd; }; #define IPS_OFFLINE 0x04 /* ---------- ips_cmdqueue_init, ips.c:152-192 (logic mirrored) ---------- */ static void ips_cmdqueue_free_sc(struct ips_softc *sc); /* ips.c free, below */ static int ips_cmdqueue_init(struct ips_softc *sc) { int i; struct ips_command *command; sc->commandarray = kmalloc_(sizeof(sc->commandarray[0]) * sc->max_cmds); for (i = 0; i < sc->max_cmds; i++) { command = &sc->commandarray[i]; command->id = i; /* bus_dmamem_alloc(sc->command_dmatag, ..., BUS_DMA_NOWAIT) */ if (bus_dmamem_alloc_fail) goto error; /* ips.c:167 */ command->command_phys_addr = 0xdead0000 + i; if (i == 0) sc->staticcmd = command; } sc->state &= ~IPS_OFFLINE; return 0; error: ips_cmdqueue_free_sc(sc); /* ips.c:190 */ return 12; /* ENOMEM */ } /* ips.c:116-143, logic mirrored (forward-declared above) */ static void ips_cmdqueue_free_sc(struct ips_softc *sc) { int i; struct ips_command *command; if (sc->used_commands == 0) { for (i = 0; i < sc->max_cmds; i++) { command = &sc->commandarray[i]; /* ips.c:124 */ if (command->command_phys_addr == 0) /* ips.c:125 UAF READ on 2nd call */ continue; /* bus_dmamem_unload / bus_dmamem_free / bus_dmamap_destroy on * stale pointers would run here */ } sc->state |= IPS_OFFLINE; /* ips.c:137 */ } sc->staticcmd = NULL; kfree_(sc->commandarray); /* ips.c:140: unconditional */ } /* ---------- ips_adapter_free, ips.c:525-548 (relevant part) ---------- */ static int ips_adapter_free(struct ips_softc *sc) { ips_cmdqueue_free_sc(sc); /* ips.c:533 */ return 0; } /* ---------- ips_adapter_init, ips.c:374-464 (relevant part) ---------- */ static int ips_adapter_init(struct ips_softc *sc) { sc->max_cmds = 1; /* ips.c:413 */ ips_cmdqueue_init(sc); /* ips.c:414: RETURN IGNORED */ /* * sc->ips_adapter_reinit(sc, 0): the card itself is healthy, register * POST checks pass -- this succeeds. [ips.c:416] */ /* * ips_ffdc_reset -> ips_get_free_cmd: state has IPS_OFFLINE (set inside * the failed init), so it returns EIO -> "failed to send ffdc reset" * -> goto error. [ips.c:421-425] */ ips_adapter_free(sc); /* ips.c:462 */ return 6; /* ENXIO */ } int main(void) { static struct ips_softc sc; /* zeroed, like device_get_softc */ bus_dmamem_alloc_fail = 1; /* force the NOWAIT allocation failure */ ips_adapter_init(&sc); printf("unreachable: allocator corruption exit expected\n"); return 0; } |