DF-3729 / 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 | /* * DF-3729 harness โ ciss_notify_rescan_logical() (sys/dev/raid/ciss/ciss.c: * 3851-3914) walks the controller-supplied REPORT_LOGICAL_LUNS reply with * NO bound on ndrives (ciss.c:3866 โ unlike ciss_init_logical, which checks * `ndrives > CISS_MAX_LOGICAL` at ciss.c:1357) and NO bounds check on * CISS_LUN_TO_BUS()/CISS_LUN_TO_TARGET() before indexing * sc->ciss_logical[bus][target] (ciss.c:3899-3901). * * Consequences with a malicious/buggy controller reply: * - cll->lun[i] is read past the end of the kmalloc'd 15-LUN report buffer * (ciss.c:3899) โ heap OOB READ feeding the index computation; * - sc->ciss_logical[bus][target] with bus in [0,255] (array rows: * ciss_max_logical_bus, typically 1-2) and target in [0,255] (row length: * CISS_MAX_LOGICAL=15) โ heap OOB WRITE at ciss.c:3906-3908 * (ld->cl_update=0, ld->cl_address, ld->cl_controller) and inside * ciss_identify_logical (ld->cl_ldrive / ld->cl_status pointer stores). * * Same code exists in FreeBSD (upstream-inherited); the pass-1 audit fixed * only the attach-time sibling (DF-1190, ciss_init_logical), not the * notify/rescan sibling. * * Build: cc -O2 -Wall -o harness harness.c * Fixed: cc -DFIXED ... clamps ndrives and validates bus/target (fix.diff). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> /* ---- real kernel constants (cissreg.h / cissvar.h) ---- */ #define CISS_MAX_LOGICAL 15 /* cissvar.h:49 */ #define CISS_LUN_TO_BUS(x) (((x) >> 16) & 0xFF) /* cissreg.h:509 */ #define CISS_LUN_TO_TARGET(x) ((x) & 0xFF) /* cissreg.h:510 */ #define REDZONE 4096 #define POISON 0xAACCEEFFUL struct ciss_lun_report_rep { /* cissreg.h:172-177 */ uint32_t list_size; /* big-endian */ uint32_t pad; uint32_t lun[]; /* 8-byte addresses; low 32 used */ }; struct ldrive_rep { /* struct ciss_ldrive (index fields) */ uint64_t cl_address; void *cl_controller; int cl_status; int cl_update; }; static int max_logical_bus = 2; /* typical sc->ciss_max_logical_bus */ static struct ldrive_rep ** alloc_logical(void) { struct ldrive_rep **rows; int i; rows = calloc(max_logical_bus, sizeof(*rows)); for (i = 0; i < max_logical_bus; i++) rows[i] = calloc(CISS_MAX_LOGICAL, sizeof(struct ldrive_rep)); return rows; } static uint32_t ntohl_(uint32_t x); /* big-endian field on x86 */ int main(void) { struct ciss_lun_report_rep *cll; struct ldrive_rep **logical; size_t report_bytes = sizeof(*cll) + CISS_MAX_LOGICAL * 8; /* ciss.c:1270 */ uint32_t fw_list_size; int ndrives, i, oob_reads = 0, oob_writes = 0, iters; printf("== DF-3729 ciss_notify_rescan_logical unbounded walk ==\n"); printf("report buffer = %zu bytes (15 LUNs) + %d-byte redzone;" " ciss_max_logical_bus=%d\n\n", report_bytes, REDZONE, max_logical_bus); /* kmalloc'd reply buffer with an unmapped-style redzone behind it */ cll = calloc(1, report_bytes + REDZONE); memset((char *)cll + report_bytes, POISON, REDZONE); logical = alloc_logical(); /* * Malicious controller data (written by the adapter into the DMA * buffer): a valid first LUN, then a list_size claiming far more * entries than the buffer holds. The firmware also simply being * buggy (returning garbage list_size) reaches the same state. */ cll->lun[0] = (0u << 16) | 3u; /* bus 0, target 3 (valid) */ cll->lun[1] = (1u << 16) | 7u; /* bus 1, target 7 (valid) */ fw_list_size = 0x00001000; /* claims 4096 LUNs */ /* ciss.c:3866 โ no CISS_MAX_LOGICAL sanity check here */ ndrives = (int)(ntohl_(fw_list_size) / 8); printf("controller list_size=0x%x -> ndrives=%d (buffer holds %d;" " init_logical would have rejected this at ciss.c:1357)\n\n", fw_list_size, ndrives, CISS_MAX_LOGICAL); #ifdef FIXED /* the fix: clamp like init_logical + validate bus/target */ if (ndrives > CISS_MAX_LOGICAL) { printf("[fix] clamping ndrives %d -> %d\n\n", ndrives, CISS_MAX_LOGICAL); ndrives = CISS_MAX_LOGICAL; } #endif iters = ndrives > 256 ? 256 : ndrives; /* cap demo runtime */ for (i = 0; i < iters; i++) { int bus, target; struct ldrive_rep *ld; int past_buffer = (size_t)((char *)&cll->lun[i] - (char *)cll) >= report_bytes; /* ciss.c:3899-3900 */ bus = CISS_LUN_TO_BUS(cll->lun[i]); /* redzone poison when * i >= 15 */ target = CISS_LUN_TO_TARGET(cll->lun[i]); if (past_buffer) oob_reads++; if (i < 8 || past_buffer) { printf(" i=%3d lun read %s (raw 0x%08x) -> bus=%3d" " target=%3d", i, past_buffer ? "OOB (redzone)" : "in-buffer", cll->lun[i], bus, target); if (bus >= max_logical_bus || target >= CISS_MAX_LOGICAL) printf(" -> INDEXES ROW %d OF %d, COL beyond %d" " (OOB WRITE)", bus, max_logical_bus, CISS_MAX_LOGICAL - 1); printf("\n"); } if (bus >= max_logical_bus || target >= CISS_MAX_LOGICAL) { oob_writes++; #ifdef FIXED continue; /* the fix rejects the entry outright */ #else continue; /* the kernel would write through the * OOB ld here (3906-3908 + identify) */ #endif } ld = &logical[bus][target]; ld->cl_update = 0; /* ciss.c:3906 */ ld->cl_address = cll->lun[i]; /* ciss.c:3907 */ ld->cl_controller = NULL; /* ciss.c:3908 */ } printf("\n"); if (oob_reads > 0 || oob_writes > 0) { printf("[BUG REPRODUCED] %d OOB heap READS past the report" " buffer and %d OOB row/col\n" "index computations -> ciss_logical[bus][target] OOB" " WRITES (cl_update,\ncl_address, cl_controller, then" " identify stores cl_ldrive/cl_lstatus pointers).\n", oob_reads, oob_writes); return 0; } printf("[FIXED] ndrives clamped to %d and bus/target validated:" " 0 OOB accesses.\n", CISS_MAX_LOGICAL); return 0; } static uint32_t ntohl_(uint32_t x) /* big-endian field on x86 */ { return ((x & 0xff) << 24) | ((x & 0xff00) << 8) | ((x >> 8) & 0xff00) | (x >> 24); } |