DF-4363 / 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 | /* * DF-4363 โ mps_config_get_man_pg10: firmware-controlled DMA heap overflow. * * Kernel code mirrored exactly (sys/dev/raid/mps/mps_config.c): * :249 cm->cm_length = le16toh(mpi_reply->Header.PageLength) * 4; * (PageLength is U8 from the IOC config-page header reply -> max 255, * so cm_length can be up to 1020 bytes) * :254 page = kmalloc(MPS_MAN_PAGE10_SIZE, M_MPT2, M_ZERO | M_INTWAIT); * (mpsvar.h:105 -> #define MPS_MAN_PAGE10_SIZE 0x5C == 92 bytes, * the ONLY fixed-size page allocation in the whole file) * :260 cm->cm_data = page; * :261 error = mps_request_polled(sc, cm); (DATAIN DMA of cm_length bytes) * * The DMA SGL is built from (cm_data, cm_length): * sys/dev/raid/mps/mps.c:2099-2101 * bus_dmamap_load(sc->buffer_dmat, cm->cm_dmamap, * cm->cm_data, cm->cm_length, mps_data_cb, cm, 0); * sys/dev/raid/mps/mps.c:2081ff mps_data_cb -> one SGE per loaded segment, * total transfer length == cm->cm_length. * bus_dmamap_load does NOT know the malloc size โ it happily maps 1020 bytes * starting at a 92-byte allocation, and the IOC then DMAs the whole * Manufacturing Page 10 (PageLength dwords, content chosen by the firmware) * past the end of the 92-byte heap object. * * Every sibling getter in this file (and every getter in the newer * sys/dev/raid/mpr/mpr_config.c) allocates cm->cm_length instead: * mps_config.c:136, 607, 730, 852, 970, 1089, 1209, 1354 * mpr_config.c:145, 277, 450, 588, 724, 861, 998, 1130, 1262, 1394, 1552 * * Harness method (same as the DF-1556/DF-1557 precedent): we cannot attach a * malicious WarpDrive HBA to the audit guest, so we reproduce the exact * kernel arithmetic and the exact write pattern in an unprivileged userland * process. The 92-byte "kmalloc" is placed at the very end of a page that is * followed by a PROT_NONE guard page; the firmware DMA is modeled as a * memcpy() of cm_length bytes of firmware-chosen data. Any PageLength >= 24 * (24*4 = 96 > 92) writes past the allocation and faults on the guard page. * * Compile: cc -O2 -Wall -Wextra -o harness harness.c * Run: ./harness <PageLength 1..255> * FIXED variant (models fix.diff): cc -DFIXED ... ; overflow must be gone. */ #include <sys/types.h> #include <sys/mman.h> #include <signal.h> #include <setjmp.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* --- verbatim constants from the kernel tree --- */ #define MPS_MAN_PAGE10_SIZE 0x5C /* sys/dev/raid/mps/mpsvar.h:105 */ #define MPS_WD_LSI_OEM 0x80 /* mpsvar.h:99 */ #define MPS_WD_HIDE_EXPOSE_MASK 0x03 /* mpsvar.h:100 */ /* Mpi2ManufacturingPagePS_t (mpi2_cnfg.h:722-727): * U8 PageType, PageNumber, PageVersion, PageLength; // 0x00 header * U32 ProductSpecificInfo; // 0x04 (used as [] array) * kernel derefs reach: page->ProductSpecificInfo (0x04) and pPS_info[5] * (0x04 + 5*4 = 0x18..0x1C) โ both inside 0x5C, so the field reads are fine; * the bug is purely the DMA length vs allocation size. */ static sigjmp_buf jmp; static volatile sig_atomic_t got_sig; static void handler(int sig) { got_sig = sig; siglongjmp(jmp, 1); } int main(int argc, char **argv) { long ps_arg; u_int page_length; /* firmware PageLength (dwords) */ u_int cm_length; /* == cm->cm_length at mps_config.c:249/131-style */ unsigned char *alloc92; /* == page at mps_config.c:254 */ unsigned char fw_page[255 * 4]; /* the "DMA" source, fw-chosen bytes */ long ps = sysconf(_SC_PAGESIZE); void *map; if (argc != 2) exit(2); ps_arg = strtol(argv[1], NULL, 0); if (ps_arg < 1 || ps_arg > 255) exit(2); page_length = (u_int)ps_arg; printf("== DF-4363 harness: mps_config_get_man_pg10 DMA heap overflow ==\n"); printf("kernel MPS_MAN_PAGE10_SIZE = 0x%02X (%d) (mpsvar.h:105)\n", MPS_MAN_PAGE10_SIZE, MPS_MAN_PAGE10_SIZE); printf("firmware Manufacturing Pg10 PageLength = %u dwords\n", page_length); /* :249 */ cm_length = page_length * 4; printf("-> cm->cm_length (PageLength*4) = %u bytes (mps_config.c:249)\n", cm_length); #ifdef FIXED { size_t fixed_sz = cm_length > MPS_MAN_PAGE10_SIZE ? cm_length : MPS_MAN_PAGE10_SIZE; printf("[FIXED] kmalloc sized MAX(cm_length, 0x5C) = %zu" " + length floor guard (fix.diff)\n", fixed_sz); if (cm_length < 0x1C) { printf("[FIXED] page shorter than ProductSpecificInfo[5]" " needs -> EINVAL (fix.diff hunk 2)\n"); exit(0); } } #endif /* kmalloc(0x5C) with a guard page immediately after it */ map = mmap(NULL, (size_t)(2 * ps), PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); if (map == MAP_FAILED) { perror("mmap"); exit(2); } if (mprotect((char *)map + ps, (size_t)ps, PROT_NONE) != 0) { perror("mprotect"); exit(2); } alloc92 = (unsigned char *)map + ps - MPS_MAN_PAGE10_SIZE; /* firmware-chosen page image: OEM byte at 0x04, pattern elsewhere */ memset(fw_page, 0xA5, sizeof(fw_page)); fw_page[4] = MPS_WD_LSI_OEM; fw_page[0x18] = 0x03; /* pPS_info[5] hide/expose value */ got_sig = 0; signal(SIGSEGV, handler); signal(SIGBUS, handler); if (sigsetjmp(jmp, 1) == 0) { if (cm_length > MPS_MAN_PAGE10_SIZE) printf("[BUGGY] IOC DMA of %u bytes into the 92-byte" " page (mps.c:2099-2101 dmamap_load of" " cm_data/cm_length) ...\n", cm_length); else printf("[OK] IOC DMA of %u bytes fits the 92-byte" " page exactly\n", cm_length); fflush(stdout); /* the device write itself: mps_data_cb SGEs cover cm_length */ #ifdef FIXED memcpy(alloc92, fw_page, cm_length > MPS_MAN_PAGE10_SIZE ? MPS_MAN_PAGE10_SIZE : cm_length); /* (fixed allocation is >= cm_length, so full-length copy would * also be safe; truncated copy models the conservative fix) */ #else memcpy(alloc92, fw_page, cm_length); #endif /* kernel-side consumers (mps_config.c:286,297-299) */ printf("page->ProductSpecificInfo OEM = 0x%02X, " "pPS_info[5] hide/expose = 0x%02X\n", alloc92[4], alloc92[0x18]); } else { printf(">>> SIGSEGV/%d caught: IOC DMA wrote past the 92-byte" " heap allocation (guard page hit)\n", got_sig); printf(">>> PRIMITIVE CONFIRMED: firmware PageLength=%u" " (cm_length=%u) overflows the 0x5C MPS_MAN_PAGE10_SIZE" " buffer by %u bytes.\n", page_length, cm_length, cm_length - MPS_MAN_PAGE10_SIZE); printf(">>> This is the exact code at" " sys/dev/raid/mps/mps_config.c:249,254,260" " (mps.c:2099-2101 builds the DMA over cm_length).\n"); exit(1); } printf("no overflow: DMA stayed inside the allocation\n"); return (0); } |