DF-4559 / df4559_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 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 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 | /* * DF-4559 harness โ proof of the sassc->targets[] OOB write in * sys/dev/raid/mps/mps_sas_lsi.c (mpssas_volume_add / IR-config REMOVED branch). * * Replicates, byte-faithfully on x86-64: * - struct mpssas_target (sys/dev/raid/mps/mps_sas.h:40-79) * - the allocation (sys/dev/raid/mps/mps_sas.c:696-697): * sassc->targets = kmalloc(sizeof(struct mpssas_target) * sc->facts->MaxTargets) * - the ID space (sys/dev/raid/mps/mps_mapping.c:1733-1734): * sc->max_volumes = facts->MaxVolumes; * sc->max_devices = facts->MaxTargets + sc->max_volumes; * - the IR volume maprange (sys/dev/raid/mps/mps_mapping.c:232-234, HIGH mode): * start_idx = max_devices - max_volumes (== MaxTargets) * end_idx = start_idx + max_volumes - 1 (== MaxTargets+MaxVolumes-1) * - the write sequence of mpssas_volume_add (mps_sas_lsi.c:847-853): * targ = &sassc->targets[id]; * targ->tid = id; targ->handle = handle; targ->devname = wwid; * TAILQ_INIT(&targ->commands); TAILQ_INIT(&targ->timedout_commands); * SLIST_INIT(&targ->luns); * - the zeroing sequence of the IR REMOVED branch (mps_sas_lsi.c:310-316): * targ->handle = 0; targ->encl_slot = 0; targ->encl_handle = 0; * targ->exp_dev_handle = 0; targ->phy_num = 0; targ->linkrate = 0; * * Scenarios (each fork()ed so a SIGSEGV is contained and reported): * voladd-HIGH : volume_add at id = max_devices - max_volumes (first volume * slot with IOC pg8 IRVolumeMappingFlags = HIGH) -> OOB write. * voladd-LOW : control, volume ids [0, max_volumes) -> in bounds, clean. * removed-HIGH: REMOVED-branch zeroing at id = max_devices-1 -> OOB write. * canary : no guard; count corrupted bytes past the array end and show * the per-field clobber offsets (the fw-controlled u64 * devname=wwid write included). * info : print sizeof/offsetof table + the u16-truncation corner * (MaxTargets+MaxVolumes > 0xFFFF) that makes ir_shutdown's * start_idx underflow (mps_sas_lsi.c:901) โ informational. * * Build: cc -O2 -o df4559 df4559_harness.c * Run: ./df4559 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <stddef.h> #include <unistd.h> #include <sys/mman.h> #include <sys/wait.h> #include <sys/queue.h> #include <signal.h> #include <errno.h> /* ---- verbatim replica of struct mpssas_target (mps_sas.h:40-79) ---- */ struct mps_command; struct mpssas_lun; struct sysctl_oid; struct sysctl_oid_entry; TAILQ_HEAD(sysctl_ctx_list, sysctl_oid_entry); struct mpssas_target { uint16_t handle; uint8_t linkrate; uint64_t devname; uint32_t devinfo; uint16_t encl_handle; uint16_t encl_slot; uint8_t flags; #define MPSSAS_TARGET_INREMOVAL (1 << 3) #define MPS_TARGET_FLAGS_RAID_COMPONENT (1 << 4) uint16_t tid; SLIST_HEAD(, mpssas_lun) luns; TAILQ_HEAD(, mps_command) commands; struct mps_command *tm; TAILQ_HEAD(, mps_command) timedout_commands; uint16_t exp_dev_handle; uint16_t phy_num; uint64_t sasaddr; uint16_t parent_handle; uint64_t parent_sasaddr; uint32_t parent_devinfo; struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; /* opaque ptr */ TAILQ_ENTRY(mpssas_target) sysctl_link; uint64_t issued; uint64_t completed; unsigned int outstanding; unsigned int timeouts; unsigned int aborts; unsigned int logical_unit_resets; unsigned int target_resets; }; /* ---- replicated driver state (types per mpsvar.h:394,397; mpi2_ioc.h:275,282) ---- */ struct facts { uint16_t MaxTargets; uint8_t MaxVolumes; }; static size_t tgt_size; static size_t n_alloc; /* entries in sassc->targets */ static uint32_t max_devices, max_volumes; static void layout_init(struct facts f) { n_alloc = f.MaxTargets; /* mps_sas.c:697 */ max_volumes = f.MaxVolumes; /* mps_mapping.c:1733 */ max_devices = (uint16_t)(f.MaxTargets + f.MaxVolumes); /* u16! :1734 */ tgt_size = sizeof(struct mpssas_target); } /* IR maprange, mps_mapping.c:220-235 */ static void ir_maprange(int high, uint32_t *start, uint32_t *end) { if (!high) { *start = 0; } else { *start = max_devices - max_volumes; /* == MaxTargets */ } *end = *start + max_volumes - 1; } /* place a "kmalloc(n_alloc * tgt_size)" so its END abuts a PROT_NONE page */ static struct mpssas_target * guarded_targets(size_t *out_end_off) { size_t ps = 4096, usable = n_alloc * tgt_size; size_t pages = (usable + ps - 1) / ps + 1; /* +1 fully slack page */ char *m = mmap(NULL, (pages + 1) * ps, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); if (m == MAP_FAILED) { perror("mmap"); exit(2); } if (mprotect(m + pages * ps, ps, PROT_NONE) != 0) { perror("mprotect"); exit(2); } *out_end_off = usable; /* array occupies [m + pages*ps - usable, m + pages*ps) */ return (struct mpssas_target *)(m + pages * ps - usable); } /* mps_sas_lsi.c:847-853 โ mpssas_volume_add write sequence, verbatim math */ static void volume_add_writes(struct mpssas_target *targets, uint32_t id, uint16_t handle, uint64_t wwid) { struct mpssas_target *targ = &targets[id]; /* :847 */ targ->tid = id; /* :848 */ targ->handle = handle; /* :849 */ targ->devname = wwid; /* :850 */ TAILQ_INIT(&targ->commands); /* :851 */ TAILQ_INIT(&targ->timedout_commands); /* :852 */ SLIST_INIT(&targ->luns); /* :853 */ } /* mps_sas_lsi.c:310-316 โ IR REMOVED branch, verbatim math */ static void removed_zeroing(struct mpssas_target *targets, uint32_t id) { struct mpssas_target *targ = &targets[id]; /* :310 */ targ->handle = 0x0; targ->encl_slot = 0x0; targ->encl_handle = 0x0; targ->exp_dev_handle = 0x0; targ->phy_num = 0x0; targ->linkrate = 0x0; } static int run_child(void (*fn)(void), const char *name) { pid_t p = fork(); if (p == 0) { fn(); _exit(0); } int st; waitpid(p, &st, 0); if (WIFSIGNALED(st)) { printf(" [%s] -> KILLED by SIGSEGV at write past allocation end " "(OOB write confirmed)\n", name); return 1; } if (WIFEXITED(st) && WEXITSTATUS(st) == 0) { printf(" [%s] -> clean exit (all writes in bounds)\n", name); return 0; } printf(" [%s] -> unexpected exit %d\n", name, WEXITSTATUS(st)); return -1; } static struct facts F = { .MaxTargets = 256, .MaxVolumes = 8 }; /* SAS2008-IR-like */ static void sc_voladd_high(void) { size_t end_off; struct mpssas_target *t = guarded_targets(&end_off); uint32_t s, e; ir_maprange(1, &s, &e); printf(" HIGH maprange [%u..%u], targets n=%zu (%zu bytes), " "first volume write at byte %zu (past end at %zu)\n", s, e, n_alloc, n_alloc * tgt_size, (size_t)s * tgt_size, end_off); volume_add_writes(t, s, 0x00fc, 0x4141414242424243ULL); /* id=MaxTargets */ printf(" BUG: write completed without fault??\n"); } static void sc_voladd_low(void) { size_t end_off; struct mpssas_target *t = guarded_targets(&end_off); uint32_t s, e; ir_maprange(0, &s, &e); printf(" LOW maprange [%u..%u] (control), all < n=%zu\n", s, e, n_alloc); for (uint32_t id = s; id <= e; id++) volume_add_writes(t, id, 0x00fc, 0x4141414242424243ULL); } static void sc_removed_high(void) { size_t end_off; struct mpssas_target *t = guarded_targets(&end_off); uint32_t s, e; ir_maprange(1, &s, &e); printf(" REMOVED zeroing at last volume slot id=%u -> byte %zu " "(end at %zu)\n", e, (size_t)e * tgt_size, end_off); removed_zeroing(t, e); printf(" BUG: write completed without fault??\n"); } /* fix model: sassc->targets sized sc->facts->MaxTargets + MaxVolumes * (== sc->max_devices), so every mapping-table id is in bounds. */ static void sc_voladd_high_fixed(void) { size_t end_off; struct mpssas_target *t; uint32_t s, e; n_alloc = max_devices; /* FIXED allocation size */ t = guarded_targets(&end_off); ir_maprange(1, &s, &e); printf(" FIXED: n=%zu entries, HIGH maprange [%u..%u] all < n\n", n_alloc, s, e); for (uint32_t id = s; id <= e; id++) volume_add_writes(t, id, 0x00fc, 0x4141414242424243ULL); n_alloc = F.MaxTargets; /* restore for any later scenario */ } static void sc_canary(void) { /* no guard; measure the corruption footprint past the array end */ size_t total = (n_alloc + 16) * tgt_size; struct mpssas_target *t = calloc(1, total); memset((char *)t + n_alloc * tgt_size, 0xAA, total - n_alloc * tgt_size); uint32_t s, e; ir_maprange(1, &s, &e); for (uint32_t id = s; id <= e; id++) { volume_add_writes(t, id, 0x00fc, 0xdeadbeefcafe0609ULL); printf(" volume id %u: writes span bytes [%zu..%zu) " "rel. to array base โ %s (offsetof devname=%zu, wwid u64 " "lands at +%zu)\n", id, (size_t)id * tgt_size, (size_t)(id + 1) * tgt_size, (id >= n_alloc) ? "PAST END" : "in bounds", offsetof(struct mpssas_target, devname), (size_t)id * tgt_size + offsetof(struct mpssas_target, devname)); } size_t first = SIZE_MAX, last = 0, cnt = 0; unsigned char *u = (unsigned char *)t; for (size_t i = n_alloc * tgt_size; i < total; i++) { if (u[i] != 0xAA) { if (first == SIZE_MAX) first = i; last = i; cnt++; } } printf(" canary result: %zu bytes clobbered past the allocation end, " "spanning [+%zu .. +%zu] (allocation was %zu bytes)\n", cnt, first, last, n_alloc * tgt_size); /* show the fw-controlled u64 landed where promised */ uint64_t got = *(uint64_t *)((char *)t + s * tgt_size + offsetof(struct mpssas_target, devname)); printf(" devname readback at first volume slot: 0x%016jx " "(expected 0xdeadbeefcafe0609) -> %s\n", (uintmax_t)got, got == 0xdeadbeefcafe0609ULL ? "MATCH: fw-controlled 8B OOB write" : "MISMATCH"); free(t); } static void sc_info(void) { printf(" sizeof(struct mpssas_target) [replica, x86-64] = %zu\n", tgt_size); printf(" offsetof: handle=%zu linkrate=%zu devname=%zu tid=%zu " "commands=%zu luns=%zu timedout=%zu\n", offsetof(struct mpssas_target, handle), offsetof(struct mpssas_target, linkrate), offsetof(struct mpssas_target, devname), offsetof(struct mpssas_target, tid), offsetof(struct mpssas_target, commands), offsetof(struct mpssas_target, luns), offsetof(struct mpssas_target, timedout_commands)); /* u16 truncation corner for ir_shutdown start_idx (mps_sas_lsi.c:901) */ struct facts f2 = { .MaxTargets = 65535, .MaxVolumes = 255 }; uint32_t md = (uint16_t)(f2.MaxTargets + f2.MaxVolumes); printf(" [informational] MaxTargets=65535 MaxVolumes=255 -> " "sc->max_devices truncates to %u (< max_volumes=%u); " "ir_shutdown start_idx = max_devices - max_volumes = %u (u32 wrap) " "-> &sc->mapping_table[0x%x] wild read (mps_sas_lsi.c:901-905)\n", md, f2.MaxVolumes, md - f2.MaxVolumes, md - f2.MaxVolumes); } int main(void) { printf("== DF-4559: sassc->targets[] OOB write (mps_sas_lsi.c volume " "registration vs mps_sas.c:697 alloc) ==\n"); layout_init(F); printf("facts: MaxTargets=%u MaxVolumes=%u -> targets n=%zu entries " "(%zu B); ID space max_devices=%u (0..%u)\n\n", F.MaxTargets, F.MaxVolumes, n_alloc, n_alloc * tgt_size, max_devices, max_devices - 1); sc_info(); printf("\n-- guarded (array end == guard page start) --\n"); run_child(sc_voladd_high, "voladd-HIGH "); run_child(sc_voladd_low, "voladd-LOW "); run_child(sc_removed_high, "removed-HIGH"); run_child(sc_voladd_high_fixed, "voladd-HIGH-FIXED"); printf("\n-- canary (corruption footprint) --\n"); sc_canary(); return 0; } |