DF-1557 / 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 | /* * DF-1557 โ userspace harness for the unchecked PhysDiskMap OOB write in * mps_wd_config_pages() (sys/dev/raid/mps/mps_config.c:460-465). * * The guest has no LSI SAS HBA, so the in-kernel mps(4) driver never * attaches and the live code path cannot be triggered from userspace. * This harness reproduces the *exact* C logic of the buggy write loop * against a struct laid out identically to the tail of struct mps_softc * (DD_column_map[] as the final field, mpsvar.h:442), with a guard page * placed immediately after. A firmware PhysDisk entry with PhysDiskMap * >= MPS_MAX_DISKS_IN_VOL writes one controlled byte at a controlled * offset past the softc allocation and faults into the guard page โ * proving the primitive. * * Layout reproduced verbatim from: * - sys/dev/raid/mps/mpsvar.h:270-273 (struct mps_column_map) * - sys/dev/raid/mps/mpsvar.h:442 (DD_column_map[MPS_MAX_DISKS_IN_VOL]) * - sys/dev/raid/mps/mpsvar.h:106 (MPS_MAX_DISKS_IN_VOL = 10) * - sys/dev/raid/mps/mpi/mpi2_cnfg.h:1314-1321 (Mpi2RaidVol0PhysDisk_t) * - sys/dev/raid/mps/mps_config.c:460-465 (the buggy loop) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <stddef.h> #include <unistd.h> #include <signal.h> #include <setjmp.h> #include <sys/mman.h> #define MPS_MAX_DISKS_IN_VOL 10 /* sys/dev/raid/mps/mpsvar.h:270-273 โ kernel struct is naturally 4 bytes */ struct mps_column_map { uint16_t dev_handle; uint8_t phys_disk_num; /* 1 byte pad -> sizeof = 4 */ }; /* sys/dev/raid/mps/mpi/mpi2_cnfg.h:1314-1321 */ typedef struct { uint8_t RAIDSetNum; uint8_t PhysDiskMap; uint8_t PhysDiskNum; uint8_t Reserved; } __attribute__((packed)) Mpi2RaidVol0PhysDisk_t; /* Tail of struct mps_softc: DD_column_map is the LAST field (mpsvar.h:442). * For the harness we only need to model the tail accurately, because * DD_column_map[].phys_disk_num is what gets written. We place it as the * final member so anything past [MPS_MAX_DISKS_IN_VOL-1] is "past the softc". */ struct mps_softc_tail { uint8_t padding[64]; /* irrelevant fields before DD_column_map */ struct mps_column_map DD_column_map[MPS_MAX_DISKS_IN_VOL]; }; static sigjmp_buf jb; static volatile int got_sig = 0; static void handler(int s) { got_sig = s; siglongjmp(jb, 1); } int main(int argc, char **argv) { long pagesz = sysconf(_SC_PAGESIZE); /* firmware-controlled: PhysDiskMap (index) and PhysDiskNum (value) */ uint8_t want_map = (argc > 1) ? (uint8_t)atol(argv[1]) : 255; uint8_t want_num = (argc > 2) ? (uint8_t)atol(argv[2]) : 0x41; uint8_t num_phys = (argc > 3) ? (uint8_t)atol(argv[3]) : 1; /* <=8 passes gate */ printf("== DF-1557 harness: PhysDiskMap OOB write in mps_wd_config_pages ==\n"); printf("sizeof(struct mps_column_map) = %zu (kernel: 4 w/ pad)\n", sizeof(struct mps_column_map)); printf("MPS_MAX_DISKS_IN_VOL = %d (kernel: 10)\n", MPS_MAX_DISKS_IN_VOL); printf("DD_column_map array bytes = %zu\n", sizeof(((struct mps_softc_tail*)0)->DD_column_map)); printf("firmware PhysDiskMap = %u (U8, 0..255)\n", want_map); printf("firmware PhysDiskNum = 0x%02x (written value)\n", want_num); printf("firmware NumPhysDisks = %u (passes line-400 gate <=8)\n", num_phys); /* Layout: [DD_column_map][guard page] โ DD_column_map tail must END exactly * at the guard page boundary, so any write past DD_column_map[9] faults. * Map 2 pages; place DD_column_map so its last byte touches the boundary. * (In the kernel, DD_column_map is the LAST field of struct mps_softc, so * writes past its end land in whatever is allocated adjacent on the * kernel heap โ exactly the primitive we are demonstrating.) */ size_t ddcmap_sz = sizeof(struct mps_column_map) * MPS_MAX_DISKS_IN_VOL; size_t maplen = pagesz * 2; char *base = mmap(NULL, maplen, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (base == MAP_FAILED) { perror("mmap"); return 2; } char *guard_page = base + pagesz; if (mprotect(guard_page, pagesz, PROT_NONE) != 0) { perror("mprotect"); return 2; } /* sc points at a struct whose DD_column_map is positioned to end at base+pagesz */ struct mps_softc_tail dummy; size_t tail_ddoffset = (char *)&dummy.DD_column_map - (char *)&dummy; /* position the struct so its DD_column_map ends at the guard page */ char *sc_raw = guard_page - tail_ddoffset - ddcmap_sz; struct mps_softc_tail *sc = (struct mps_softc_tail *)sc_raw; memset(sc, 0xCC, tail_ddoffset + ddcmap_sz); /* Construct a RAID Volume Page 0 with NumPhysDisks PhysDisk entries. * The line-400 gate (NumPhysDisks > 8) is satisfied. */ Mpi2RaidVol0PhysDisk_t rvpd[8]; memset(rvpd, 0, sizeof(rvpd)); for (int i = 0; i < num_phys && i < 8; i++) { rvpd[i].PhysDiskMap = want_map; rvpd[i].PhysDiskNum = want_num; } /* Reproduce the EXACT kernel code at mps_config.c:460-465: * pRVPD = (pMpi2RaidVol0PhysDisk_t)&raid_vol_pg0->PhysDisk; * for (index = 0; index < raid_vol_pg0->NumPhysDisks; index++) { * sc->DD_column_map[pRVPD->PhysDiskMap].phys_disk_num = * pRVPD->PhysDiskNum; * pRVPD++; * } */ struct sigaction sa = {0}; sa.sa_handler = handler; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGBUS, &sa, NULL); uint32_t byte_offset = (uint32_t)want_map * sizeof(struct mps_column_map) + offsetof(struct mps_column_map, phys_disk_num); uint32_t past_end = (byte_offset >= sizeof(sc->DD_column_map)) ? byte_offset - (uint32_t)sizeof(sc->DD_column_map) : 0; printf("\nwrite target: &DD_column_map[%u].phys_disk_num " "= byte offset %u from DD_column_map base\n", want_map, byte_offset); if (past_end) printf("-> %u bytes PAST end of DD_column_map (= past end of " "struct mps_softc, since DD_column_map is the final field)\n", past_end); if (want_map < MPS_MAX_DISKS_IN_VOL) { printf("PhysDiskMap=%u < MPS_MAX_DISKS_IN_VOL=%d: in-bounds write, " "no OOB.\n(use: %s <PhysDiskMap> [num] [cnt] with map>=10)\n", want_map, MPS_MAX_DISKS_IN_VOL, argv[0]); sc->DD_column_map[want_map].phys_disk_num = want_num; printf("wrote in-bounds: DD_column_map[%u].phys_disk_num = 0x%02x\n", want_map, want_num); munmap(base, maplen); return 0; } printf("[BUGGY] running loop: sc->DD_column_map[%u].phys_disk_num = " "0x%02x ...\n", want_map, want_num); if (sigsetjmp(jb, 1) == 0) { /* Unchecked index, exactly as in the kernel. */ Mpi2RaidVol0PhysDisk_t *pRVPD = rvpd; for (int index = 0; index < num_phys; index++) { sc->DD_column_map[pRVPD->PhysDiskMap].phys_disk_num = pRVPD->PhysDiskNum; pRVPD++; } printf("!!! BUG NOT REPRODUCED โ write happened without fault? " "(guard page missed)\n"); munmap(base, maplen); return 3; } printf(">>> SIGSEGV/%d caught: controlled-byte OOB write past " "DD_column_map[%d] confirmed.\n", got_sig, MPS_MAX_DISKS_IN_VOL-1); printf(">>> PRIMITIVE CONFIRMED: a malicious HBA returning a RAID Volume " "Page 0 with PhysDiskMap=%u writes byte 0x%02x at offset +%u past " "the end of struct mps_softc.\n", want_map, want_num, past_end); printf(">>> This is the exact code at sys/dev/raid/mps/mps_config.c:462 " "(no bounds check on PhysDiskMap).\n"); munmap(base, maplen); return 0; } |