DF-4560 / df4560_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 | /* * DF-4560 harness โ proof of the fixed-offset event-member reads that run * without any validation that EventDataLength covers the structure, in * sys/dev/raid/mps/mps_sas_lsi.c mpssas_fw_work(). * * Allocation replicated verbatim from mpssas_evt_handler * (mps_sas_lsi.c:140-148): * u16 sz = le16toh(event->EventDataLength) * 4; (u16 truncation = DF-1696) * fw_event->event_data = kmalloc(sz, ...); * bcopy(event->EventData, fw_event->event_data, sz); * For each EventDataLength EDL the accessible buffer is exactly EDL*4 bytes. * * Sinks replicated (offsets from the MPI2 headers): * IR CONFIG CHANGE LIST (struct at mpi2_ioc.h:699-710, element :672-680): * event_data->Flags u32 @ 0x04 (mps_sas_lsi.c:273) * element->VolDevHandle u16 @ 0x0A (mps_sas_lsi.c:276-279 โ * read happens BEFORE the NumElements loop and regardless of * NumElements, so DF-1697's loop fix does not cover it) * LOG ENTRY ADDED (struct at mpi2_ioc.h:516-528): * logEntry->LogEntryQualifier u16 @ 0x0E (mps_sas_lsi.c:515) * logEntry->LogData[0] u8 @ 0x14 (mps_sas_lsi.c:518) * * Each case is fork()ed with the kmalloc'd buffer placed so its END abuts a * PROT_NONE guard page: an OOB member read faults (SIGSEGV) exactly as the * kernel would take a page fault / slab-boundary violation. * * Also prints (no deref, pure arithmetic): * - the EDL table: allocation size vs. bytes each sink needs; * - the DF-4561 (ir_shutdown off-by-one) IR-range coverage count: * end_idx = start_idx + max_volumes - 1 (mps_sas_lsi.c:902) * for (id = start_idx; id < end_idx; id++) (:904) * -> visits max_volumes-1 slots, never id == end_idx: the LAST volume * slot is skipped when looking for volumes to notify at shutdown. * * Build: cc -O2 -o df4560 df4560_harness.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <unistd.h> #include <sys/mman.h> #include <sys/wait.h> /* struct member offsets (u8-accurate from the MPI2 headers) */ #define IR_FLAGS_OFF 0x04 /* u32 */ #define IR_VOLDEVHANDLE_OFF 0x0A /* u16, ConfigElement[0]+2 */ #define LOG_QUAL_OFF 0x0E /* u16 */ #define LOG_DATA0_OFF 0x14 /* u8 */ static char * guarded_buf(size_t sz) { size_t ps = 4096; size_t pages = (sz + ps - 1) / ps + 1; 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); } return m + pages * ps - sz; /* buffer end == guard start */ } static int cases_run, cases_oob; static void case_ir(int edl_words) { /* mpssas_fw_work IR CONFIG CHANGE LIST, before/inside the loop */ char *event_data = guarded_buf((size_t)edl_words * 4); volatile uint32_t flags; volatile uint16_t volh; flags = *(volatile uint32_t *)(event_data + IR_FLAGS_OFF); /* :273 */ /* :276-279 โ unconditional, happens even when NumElements == 0 */ volh = *(volatile uint16_t *)(event_data + IR_VOLDEVHANDLE_OFF); (void)flags; (void)volh; printf(" EDL=%d (%d B): read completed in-bounds\n", edl_words, edl_words * 4); } static void case_log(int edl_words) { /* mpssas_fw_work LOG ENTRY ADDED, WarpDrive branch (qualifier 0x8002) */ char *event_data = guarded_buf((size_t)edl_words * 4); volatile uint16_t qual; volatile uint8_t code; if (edl_words >= 4) *(uint16_t *)(event_data + LOG_QUAL_OFF) = 0x8002; /* MPI2_WD_LOG_ENTRY (mpsvar.h:111) โ put the qualifier on-path whenever writing it is itself in bounds */ qual = *(volatile uint16_t *)(event_data + LOG_QUAL_OFF); /* :515 */ if (qual == 0x8002) code = *(volatile uint8_t *)(event_data + LOG_DATA0_OFF); /* :518 */ (void)code; printf(" EDL=%d (%d B): read completed in-bounds\n", edl_words, edl_words * 4); } static int run(int (*should_fault)(int), void (*fn)(int), const char *tag, int edl) { pid_t p = fork(); if (p == 0) { fn(edl); _exit(0); } int st; waitpid(p, &st, 0); cases_run++; if (WIFSIGNALED(st)) { cases_oob += should_fault(edl) ? 1 : 0; printf(" [%s EDL=%d] SIGSEGV โ %s\n", tag, edl, should_fault(edl) ? "OOB read past the EventDataLength-sized allocation (expected)" : "UNEXPECTED fault"); return 1; } printf(" [%s EDL=%d] clean โ %s\n", tag, edl, should_fault(edl) ? "UNEXPECTED: sink read OOB without fault" : "in bounds (expected)"); return 0; } static int ir_needs(int edl) { return edl * 4 < 12; } /* VolDevHandle needs 12 */ static int log_needs(int edl) { return edl * 4 < 21; } /* LogData[0] needs 21 */ int main(void) { printf("== DF-4560: unvalidated fixed-offset event-member reads " "(mps_sas_lsi.c:273,276-279,515,518) ==\n\n"); printf("allocation: sz = EventDataLength*4 bytes (mps_sas_lsi.c:140-141)\n"); printf("IR sink needs >= 12 B (Flags@4..8 + VolDevHandle@10..12)\n"); printf("LOG sink needs >= 21 B (Qualifier@14..16 + LogData[0]@20..21)\n\n"); printf("-- IR CONFIG CHANGE LIST: element->VolDevHandle read at +0x0A --\n"); run(ir_needs, case_ir, "IR ", 2); /* 8 B alloc, read @10 โ OOB */ run(ir_needs, case_ir, "IR ", 3); /* 12 B alloc โ exact fit, control */ printf("-- LOG ENTRY ADDED: LogData[0] read at +0x14 --\n"); run(log_needs, case_log, "LOG", 1); /* 4 B โ qualifier itself OOB */ run(log_needs, case_log, "LOG", 4); /* 16 B โ qualifier ok, LogData OOB */ run(log_needs, case_log, "LOG", 6); /* 24 B โ fits, control */ printf("\n-- arithmetic table (no deref) --\n"); for (int edl = 0; edl <= 6; edl++) { printf(" EDL=%d: alloc=%2d B | IR VolDevHandle@10 %s | " "LOG Qualifier@14 %s | LOG LogData@20 %s\n", edl, edl * 4, edl * 4 < 12 ? "OOB" : "ok ", edl * 4 < 16 ? "OOB" : "ok ", edl * 4 < 21 ? "OOB" : "ok "); } printf("\n-- DF-4561 arithmetic: mpssas_ir_shutdown volume scan coverage " "(mps_sas_lsi.c:902-911) --\n"); unsigned max_volumes = 8, start_idx = 256, end_idx; unsigned visited = 0, last_visited; end_idx = start_idx + max_volumes - 1; for (unsigned id = start_idx; id < end_idx; id++) { visited++; last_visited = id; } printf(" IR range [%u..%u] (%u slots); loop visits %u, last visited " "%u -> slot %u (the LAST volume slot) NEVER checked for a volume " "before deciding whether to send SYSTEM_SHUTDOWN_INITIATED\n", start_idx, end_idx, max_volumes, visited, last_visited, end_idx); printf("== %d cases, %d confirmed OOB ==\n", cases_run, cases_oob); return 0; } |