DF-4834 / df4834_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 | /* * DF-4834 harness โ mrsas_passthru() raw-user-pointer validation->use TOCTOU * OOB write past the 16-entry stack arrays. * * sys/dev/raid/mrsas/mrsas.c:1151 hands the RAW userland a_data pointer to * mrsas_passthru(); mrsas_ioctl.c then: * :186 validates `user_ioc->sge_count > MAX_IOCTL_SGE(16)` โ reads USER mem * :225 `for (i = 0; i < user_ioc->sge_count; i++)` โ RE-reads USER mem * ... bus_dma_tag_create(..., &ioctl_data_tag[i]) โ writes stack[i] * :307 copyout loop re-reads sge_count/sgl[] a second time (after the * multi-millisecond firmware command โ a huge window) * :354 cleanup loop re-reads a third time -> bus_dmamap_unload(tag[i]) * * This harness replicates the struct layout (mrsas_ioctl.h:80-93, packed), * the four stack arrays and the verbatim loop of :225-264, with the arrays * placed just below a guard page (overflow in the kernel runs toward the * caller frames / saved registers / return address of mrsas_passthru). * * Arms: * race โ second thread flips sge_count 16 -> 0x20000 right after the * kernel passed the :186 check (pipe handshake, deterministic) * window2 โ deterministic: flip during the (mocked) firmware command; * copyout loop (:307) then reads ioctl_data_mem[i] past the array * fixed โ kernel-copy model (copyin at ioctl entry, as in fix.diff): * racer writes are invisible, loops stay bounded -> clean exit */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <unistd.h> #include <signal.h> #include <setjmp.h> #include <pthread.h> #include <sys/mman.h> #define MAX_IOCTL_SGE 16 #define MEGAMFI_RAW_FRAME_SIZE 128 #pragma pack(1) struct mrsas_iocpacket_h { /* mrsas_ioctl.h:80-92 verbatim */ uint16_t host_no; uint16_t __pad1; uint32_t sgl_off; uint32_t sge_count; uint32_t sense_off; uint32_t sense_len; union { uint8_t raw[MEGAMFI_RAW_FRAME_SIZE]; struct { uint8_t cmd, sense_len, cmd_status, scsi_status; } hdr; } frame; struct { void *iov_base; size_t iov_len; } sgl[MAX_IOCTL_SGE]; }; #pragma pack() static long g_tag_allocs; static int mock_tag_create(void **dmat) { /* kmalloc'd tag lands in *dmat */ *dmat = (void *)(0xdead000000000000UL + (__sync_fetch_and_add(&g_tag_allocs, 1) + 1)); return 0; } static void *g_region; /* arrays + canary + guard */ #define ZONE 4096 #define GUARDOFF (2 * ZONE) /* tag array | mem array | guard */ static sigjmp_buf jmp; static volatile sig_atomic_t g_hit_guard; static void onsegv(int sig) { (void)sig; g_hit_guard = 1; siglongjmp(jmp, 1); } /* shared "user memory" that the racing thread mutates while kernel reads it */ static struct mrsas_iocpacket_h *u_ioc; /* stands for the userland arg */ static int p2c[2], c2p[2]; /* handshake pipes */ /* the verbatim shape of mrsas_ioctl.c:225-264. * volatile stands in for the kernel reality that every loop iteration calls * opaque externs (bus_dma_tag_create/bus_dmamem_alloc/copyin) which force * the compiler to re-read *user_ioc from (user) memory each time. */ static void passthru_loop(struct mrsas_iocpacket_h *ioc_, void *tagarr) { volatile struct mrsas_iocpacket_h *user_ioc = ioc_; long i; for (i = 0; i < (long)user_ioc->sge_count; i++) { /* :225 RE-reads user mem */ if (!user_ioc->sgl[i].iov_len) continue; /* :226 */ /* bus_dma_tag_create(..., &ioctl_data_tag[i]) :229-240 */ mock_tag_create((void **)((char *)tagarr + 0 * ZONE + i * 8)); /* bus_dmamem_alloc/bus_dmamap_load populate mem[i]/phys[i] :241-255 */ *(void **)((char *)tagarr + 1 * ZONE + i * 8) = (void *)(0xcafe0000UL + i); } } static void *racer(void *arg) { long flip_to = (long)(intptr_t)arg; char b = 'x'; (void)read(p2c[0], &b, 1); /* wait until kernel passed :186 */ u_ioc->sge_count = (uint32_t)flip_to; /* TOCTOU flip in USER memory */ (void)write(c2p[1], &b, 1); return NULL; } static int run_race(long flip_to) { pthread_t th; int rc; char b; g_hit_guard = 0; memset(g_region, 0, GUARDOFF); u_ioc->sge_count = 16; /* passes the :186 validation */ rc = pthread_create(&th, NULL, racer, (void *)(intptr_t)flip_to); if (rc != 0) { printf(" pthread_create failed: %d\n", rc); exit(2); } signal(SIGSEGV, onsegv); signal(SIGBUS, onsegv); if (sigsetjmp(jmp, 1) == 0) { /* :186 */ if (u_ioc->sge_count > MAX_IOCTL_SGE) { printf(" rejected\n"); return -1; } (void)write(p2c[1], &b, 1); /* racer fires NOW */ (void)read(c2p[0], &b, 1); /* flip is in */ passthru_loop(u_ioc, g_region); pthread_join(th, NULL); return 0; /* no fault this run */ } pthread_join(th, NULL); return 1; /* faulted = OOB write confirmed */ } static void report_canary(void) { long i, first = -1, last = -1; for (i = 16; i < GUARDOFF / 8; i++) { uint64_t v = *(uint64_t *)((char *)g_region + i * 8); if (v && first < 0) first = i; if (v) last = i; } if (first < 0) { printf(" canary: untouched\n"); return; } printf(" canary: first OOB slot=%ld (byte +%ld past the 16-entry arrays), " "last=%ld\n", first, (first - 16) * 8, last); } /* window-2 arm: copyout loop (:307) reading ioctl_data_mem[i] past the array * after the count flipped during the (mocked) firmware command */ static void run_window2(void) { memset(g_region, 0, GUARDOFF); for (int i = 0; i < 16; i++) *(void **)((char *)g_region + 1 * ZONE + i * 8) = (void *)(0xcafe0000UL + i); /* past-array slots hold "stack garbage" โ model adjacent-frame values */ for (int i = 16; i < 24; i++) *(void **)((char *)g_region + 1 * ZONE + i * 8) = (void *)(0xfffff00012345678UL + i); u_ioc->sge_count = 16; /* validated at :186 */ usleep(2000); /* :301 firmware command runs ~ms */ u_ioc->sge_count = 24; /* raced during the command */ printf(" copyout loop (:307) with raced sge_count=%u:\n", u_ioc->sge_count); for (long i = 16; i < (long)u_ioc->sge_count; i++) { void *src = *(void **)((char *)g_region + 1 * ZONE + i * 8); printf(" copyout(ioctl_data_mem[%ld] = %p /* kernel stack value */," " user iov_base, iov_len)\n", i, src); } } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); if (pipe(p2c) || pipe(c2p)) { perror("pipe"); return 2; } g_region = mmap(NULL, GUARDOFF + 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); mprotect((char *)g_region + GUARDOFF, 4096, PROT_NONE); u_ioc = mmap(NULL, sizeof(*u_ioc) + 65536, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); memset(u_ioc, 0, sizeof(*u_ioc) + 65536); for (int i = 0; i < MAX_IOCTL_SGE + 64; i++) u_ioc->sgl[i].iov_len = 128; u_ioc->frame.hdr.cmd = 0x03; /* MFI_CMD_LD_SCSI_IO */ printf("sizeof(struct mrsas_iocpacket)=%zu (kernel packed: 400)\n", sizeof(struct mrsas_iocpacket_h)); printf("[arm 1] race at the validation->use boundary (:186 check vs :225 loop)\n"); int r = run_race(0x20000); if (r == 1) printf(" -> SIGSEGV at guard page: ioctl_data_tag[i]/ioctl_data_mem[i] " "written OUT OF BOUNDS, i ran toward 0x20000\n"); else if (r == 0) printf(" -> loop completed without fault (flip lost)\n"); report_canary(); printf("\n[arm 2] wide window: flip during firmware command; " "copyout loop (:307) โ deterministic\n"); run_window2(); printf("\n[arm 3] FIXED model: kernel copy of the packet (copyin at entry)\n"); { struct mrsas_iocpacket_h kcopy; memcpy(&kcopy, u_ioc, sizeof(kcopy)); kcopy.sge_count = 16; u_ioc->sge_count = 0x20000; /* racer flips USER memory only */ memset(g_region, 0, GUARDOFF); passthru_loop(&kcopy, g_region); int touched = 0; /* past-end of tag array (page 0, bytes >= 16*8) and of the mem * array (page 1, bytes >= ZONE+16*8); guard page untouched = clean */ for (long off = 16 * 8; off < ZONE; off += 8) if (*(uint64_t *)((char *)g_region + off)) touched = 1; for (long off = ZONE + 16 * 8; off < GUARDOFF; off += 8) if (*(uint64_t *)((char *)g_region + off)) touched = 1; if (*(uint64_t *)((char *)g_region + 16 * 8 - 8) == 0) { printf(" internal error: array slot [15] not written\n"); return 2; } printf(" loops stayed in [0,16): clean exit; writes past arrays: %s\n", touched ? "SEEN (BUG)" : "none"); } return 0; } |