DF-4008 / diag_statemachine.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 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | /* * DF-4008 userspace replication โ mpr diag-buffer registration state machine * * Reproduces, verbatim modulo bus_dma stubs, the state transitions of * mpr_diag_register() / mpr_diag_unregister() / mpr_diag_read_buffer() * (sys/dev/raid/mpr/mpr_user.c:1442-1698, 1771-1840) and demonstrates the * three failure shapes caused by the shared sc->fw_diag_{dmat,buffer,map} * trio being overwritten by a registration of a SECOND buffer type: * * 1. stale-size OOB read (read bound uses list[TRACE].size while the * live allocation is SNAPSHOT's, smaller) * 2. cross-type free (unregister of TRACE frees SNAPSHOT's memory) * 3. NULL-dmat deref / UAF (subsequent SNAPSHOT read derefs freed or * NULL'd sc->fw_diag_dmat / fw_diag_buffer) * * Precondition (hardware-gated): the IOC advertises >=2 diag capabilities so * that >=2 fw_diag_buffer_list[] entries have enabled=TRUE (mpr.c:588-599) * and DIAG_BUFFER_POST succeeds for the first registration. */ #include <stdio.h> #include <string.h> #include <stdint.h> #define MPI2_DIAG_BUF_TYPE_TRACE 0 #define MPI2_DIAG_BUF_TYPE_SNAPSHOT 1 #define MPI2_DIAG_BUF_TYPE_COUNT 3 #define MPR_FW_DIAG_INVALID_UID 0 #define MPR_FW_DIAGNOSTIC_UID_NOT_FOUND 0xFF #define MPR_FW_DIAG_TYPE_REGISTER 1 #define MPR_FW_DIAG_TYPE_UNREGISTER 2 typedef struct { uint32_t unique_id; int enabled, immediate, valid_data, owned_by_firmware; int force_release, extended_type, buffer_type; uint32_t size; uint32_t product_specific[23]; } fw_diag_buffer_t; struct fake_sc { fw_diag_buffer_t list[MPI2_DIAG_BUF_TYPE_COUNT]; /* shared trio โ mprvar.h:410-413 */ void *fw_diag_buffer; uint64_t fw_diag_busaddr; void *fw_diag_dmat; void *fw_diag_map; /* model of the CURRENT live allocation */ size_t live_alloc_sz; int live_alloc_freed; /* bus_dma op counters */ int n_tag_create, n_tag_destroy, n_mem_alloc, n_mem_free; int post_succeeds; /* modeled HW behavior */ }; static uint8_t get_bufnum(struct fake_sc *sc, uint32_t uid) { for (uint8_t i = 0; i < MPI2_DIAG_BUF_TYPE_COUNT; i++) if (sc->list[i].unique_id == uid) return (i); return (MPR_FW_DIAGNOSTIC_UID_NOT_FOUND); } /* stubbed mpr_post_fw_diag_buffer(): models a successful IOC post */ static int post_ok(struct fake_sc *sc, fw_diag_buffer_t *b) { if (!b->enabled || !sc->post_succeeds) return (1); b->valid_data = 1; b->owned_by_firmware = 1; return (0); } /* ---- verbatim mpr_diag_register() core (mpr_user.c:1442-1638) ---- */ static int diag_register(struct fake_sc *sc, uint8_t extended_type, uint8_t buffer_type, uint32_t buffer_size, uint32_t unique_id) { fw_diag_buffer_t *pBuffer; uint8_t i; int status; if (buffer_type >= MPI2_DIAG_BUF_TYPE_COUNT) return (1); i = get_bufnum(sc, unique_id); pBuffer = &sc->list[buffer_type]; if (i != MPR_FW_DIAGNOSTIC_UID_NOT_FOUND) return (1); if ((pBuffer->unique_id != MPR_FW_DIAG_INVALID_UID) || (unique_id == MPR_FW_DIAG_INVALID_UID)) return (1); if (pBuffer->immediate && pBuffer->owned_by_firmware && (pBuffer->unique_id == MPR_FW_DIAG_INVALID_UID)) { pBuffer->immediate = 0; pBuffer->unique_id = unique_id; return (0); } if (!pBuffer->enabled) return (1); /* bus_dma_tag_create(&sc->fw_diag_dmat) โ mpr_user.c:1508 */ sc->n_tag_create++; /* OVERWRITES any prior tag */ sc->fw_diag_dmat = (void *)0x2000; sc->fw_diag_map = (void *)0x3000; /* bus_dmamem_alloc(&sc->fw_diag_buffer) โ 1523 */ sc->n_mem_alloc++; /* OVERWRITES any prior buffer */ sc->fw_diag_buffer = (void *)0x1000; /* new live allocation */ sc->live_alloc_sz = buffer_size; sc->live_alloc_freed = 0; pBuffer->size = buffer_size; /* 1596 */ pBuffer->buffer_type = buffer_type; pBuffer->immediate = 0; pBuffer->extended_type = extended_type; pBuffer->unique_id = unique_id; /* 1611 */ status = post_ok(sc, pBuffer); /* 1612 */ if (status) { /* bailout: 1619-1633 */ sc->fw_diag_busaddr = 0; sc->fw_diag_buffer = NULL; sc->fw_diag_dmat = NULL; sc->live_alloc_freed = 1; sc->n_mem_free++; sc->n_tag_destroy++; } return (status); } /* ---- verbatim mpr_diag_unregister() core (mpr_user.c:1641-1698) ---- */ static int diag_unregister(struct fake_sc *sc, uint32_t unique_id) { fw_diag_buffer_t *pBuffer; uint8_t i; int status; i = get_bufnum(sc, unique_id); if (i == MPR_FW_DIAGNOSTIC_UID_NOT_FOUND) return (1); pBuffer = &sc->list[i]; if (!pBuffer->owned_by_firmware) status = 0; else { /* modeled successful MPI2_FUNCTION_DIAG_RELEASE */ status = 0; pBuffer->owned_by_firmware = 0; } pBuffer->unique_id = MPR_FW_DIAG_INVALID_UID; /* 1680 */ if (status == 0) { /* 1681-1695 */ /* frees the SHARED trio โ whoever it belongs to now */ sc->fw_diag_busaddr = 0; sc->fw_diag_buffer = NULL; sc->fw_diag_dmat = NULL; sc->live_alloc_freed = 1; sc->n_mem_free++; sc->n_tag_destroy++; } return (status); } /* ---- verbatim mpr_diag_read_buffer() core (mpr_user.c:1771-1815) ---- */ static int diag_read(struct fake_sc *sc, uint32_t uid, uint32_t off, uint32_t bytes) { fw_diag_buffer_t *pBuffer; uint8_t i; i = get_bufnum(sc, uid); if (i == MPR_FW_DIAGNOSTIC_UID_NOT_FOUND) return (1); pBuffer = &sc->list[i]; if (off + bytes > pBuffer->size) { /* 1798: the bound that lies */ printf(" [read uid=%#x] bound REJECTED (off=%u bytes=%u > " "size=%u)\n", uid, off, bytes, pBuffer->size); return (1); } /* 1805: bus_dmamap_sync(sc->fw_diag_dmat, ...) โ dmat may be NULL */ if (sc->fw_diag_dmat == NULL) { printf(" [read uid=%#x] bus_dmamap_sync(NULL) => KERNEL " "NULL-DEREF PANIC here (mpr_user.c:1805)\n", uid); return (-1); } /* 1812-1814: pData = fw_diag_buffer + off; copyout(pData, ..., bytes) */ if (sc->live_alloc_freed) printf(" [read uid=%#x] copyout from FREED allocation " "(UAF read, mpr_user.c:1812)\n", uid); else if (off + bytes > sc->live_alloc_sz) printf(" [read uid=%#x] copyout OOB READ: bound check used " "stale size=%u but live allocation is %zu bytes -> %u " "bytes past the end disclosed to user (mpr_user.c:1798 " "vs 1814)\n", uid, pBuffer->size, sc->live_alloc_sz, (off + bytes) - (uint32_t)sc->live_alloc_sz); else printf(" [read uid=%#x] in-bounds of live alloc (unexpected)\n", uid); return (0); } int main(void) { struct fake_sc sc; memset(&sc, 0, sizeof(sc)); sc.post_succeeds = 1; /* SAS3 FW accepts DIAG_BUFFER_POST */ /* mpr.c:588-599: IOC advertises TRACE + SNAPSHOT -> both enabled */ sc.list[MPI2_DIAG_BUF_TYPE_TRACE].enabled = 1; sc.list[MPI2_DIAG_BUF_TYPE_SNAPSHOT].enabled = 1; printf("== DF-4008 replication: two enabled diag buffer types ==\n"); printf("1) REGISTER uid=0xA type=TRACE size=0x100000 (1MB)\n"); if (diag_register(&sc, 0, MPI2_DIAG_BUF_TYPE_TRACE, 0x100000, 0xA)) { printf(" register A failed\n"); return (1); } printf(" TRACE.size=%u live_alloc=%zu\n", sc.list[MPI2_DIAG_BUF_TYPE_TRACE].size, sc.live_alloc_sz); printf("2) REGISTER uid=0xB type=SNAPSHOT size=0x1000 (4KB)\n"); if (diag_register(&sc, 0, MPI2_DIAG_BUF_TYPE_SNAPSHOT, 0x1000, 0xB)) { printf(" register B failed\n"); return (1); } printf(" tag_create=%d mem_alloc=%d (prior tag+buffer LEAKED); " "live_alloc now %zu bytes, but TRACE.size is still %u\n", sc.n_tag_create, sc.n_mem_alloc, sc.live_alloc_sz, sc.list[MPI2_DIAG_BUF_TYPE_TRACE].size); printf("3) READ uid=0xA off=0 bytes=0x100000\n"); diag_read(&sc, 0xA, 0, 0x100000); printf("4) UNREGISTER uid=0xA (TRACE)\n"); diag_unregister(&sc, 0xA); printf(" shared trio freed: mem_free=%d tag_destroy=%d โ this was " "SNAPSHOT's allocation (cross-type free)\n", sc.n_mem_free, sc.n_tag_destroy); printf("5) READ uid=0xB off=0 bytes=0x1000\n"); diag_read(&sc, 0xB, 0, 0x1000); printf("== replication complete ==\n"); return (0); } |