DF-1919 / 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 | /* * DF-1919 source-confirmation harness โ mrsas_passthru integer truncation * size_t -> int in iov_len splits the DMA allocation size from the copyin * length, producing a deterministic large kernel heap overflow with * attacker-supplied bytes. * * sys/dev/raid/mrsas/mrsas_ioctl.c:161 * int i, adapter, ioctl_data_size, ioctl_sense_size, ret=0; * * sys/dev/raid/mrsas/mrsas_ioctl.c:228 * ioctl_data_size = user_ioc->sgl[i].iov_len; <-- size_t -> int truncation * * sys/dev/raid/mrsas/mrsas_ioctl.c:233-235 * bus_dma_tag_create(..., ioctl_data_size, 1, ioctl_data_size, ...) * ^maxsize ^maxsegsize (both int) * * sys/dev/raid/mrsas/mrsas_ioctl.c:241-242 * bus_dmamem_alloc(ioctl_data_tag[i], &ioctl_data_mem[i], ...) <-- allocates * ioctl_data_size * bytes * * sys/dev/raid/mrsas/mrsas_ioctl.c:255 * kern_sge32[i].length = user_ioc->sgl[i].iov_len; <-- size_t -> u32: * firmware sees the * low 32 bits only. * * sys/dev/raid/mrsas/mrsas_ioctl.c:258-259 * ret = copyin(user_ioc->sgl[i].iov_base, ioctl_data_mem[i], * user_ioc->sgl[i].iov_len); <-- uses the ORIGINAL size_t * iov_len, NOT ioctl_data_size * * user_ioc->sgl[i].iov_len is `size_t` (sys/_iovec.h:45) = u64 on amd64. * ioctl_data_size is `int` (signed 32). Picking iov_len = 0x100000008: * * ioctl_data_size = (int)0x100000008 = (int)0x00000008 = 8 (no sign) * -> bus_dma_tag_create maxsize=8, bus_dmamem_alloc allocates 8 bytes * kern_sge32[i].length = (u32)0x100000008 = 8 (firmware * DMA stays small) * copyin(iov_base, 8-byte-buf, (size_t)0x100000008 = 4 GiB + 8) * * i.e. the kernel allocates 8 bytes and then copies up to 4 GiB+8 of * attacker-supplied data into that 8-byte DMA buffer โ a deterministic, * attacker-supplied-byte kernel heap overflow. copyin stops on the first * unmapped user page, but a mmap/MADV_POPULATE-preloaded (or simply * heap-spray-filled) user buffer keeps it going. The SGE handoff to the * firmware is small (8 bytes), so the firmware itself does not amplify or * fault โ the corruption is purely kernel-side via copyin. * * Variant picks: iov_len = 0x100000000 (ioctl_data_size = 0 -> bus_dma_tag * refuses 0 maxsize, ENOMEM), iov_len = 0x1FFFFFFFF (ioctl_data_size = -1 * -> tag create may reject; depends on bus_dma backend). 0x100000008 is * the cleanest positive small value. Picking larger low-32 values like * 0x100001000 gives ioctl_data_size = 0x1000 = 4096 (still << iov_len). * * Why a harness: same Phase-6 blocker as DF-1917/1918 โ no LSI MegaRAID * SAS HBA in the QEMU audit guest (verified: pciconf -lv lists none; * /dev/mrsas* absent), so mrsas_passthru is unreachable at runtime, and * /dev/mrsas0 would be 0660 root:operator (mrsas.c:790) which maxx * (uid 1001, not in operator) cannot open anyway. * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define MRSAS_MFI_FRAME_SIZE 1024 /* mrsas.h:876 -- not used directly here */ /* Userspace-provided iovec length, as the kernel sees it (size_t / u64). */ typedef uint64_t size_t_model; /* Verbatim mrsas_ioctl.c:161 / 228 / 241 / 255 / 258 arithmetic. * Returns: alloc_size (what bus_dmamem_alloc gave), firmware_sge_length * (what kern_sge32[i].length becomes), copyin_len (what copyin actually * copies), and the resulting overflow = copyin_len - alloc_size. */ struct outcome { long alloc_size; uint32_t fw_sge_len; unsigned long long copyin_len; unsigned long long overflow; }; static struct outcome model(size_t_model iov_len) { int ioctl_data_size; /* mrsas_ioctl.c:161 */ struct outcome o; /* mrsas_ioctl.c:228: ioctl_data_size = user_ioc->sgl[i].iov_len */ ioctl_data_size = (int)iov_len; /* explicit size_t->int truncation */ /* mrsas_ioctl.c:241-242: bus_dmamem_alloc(tag, &mem, ...) allocates * ioctl_data_size bytes when ioctl_data_size > 0. (If <= 0 the tag * create fails; we model the success path.) */ o.alloc_size = (ioctl_data_size > 0) ? (long)ioctl_data_size : -1L; /* mrsas_ioctl.c:255: kern_sge32[i].length = iov_len (size_t -> u32) */ o.fw_sge_len = (uint32_t)iov_len; /* mrsas_ioctl.c:258-259: copyin(iov_base, mem, iov_len) -- uses * the ORIGINAL size_t iov_len, NOT ioctl_data_size. */ o.copyin_len = (unsigned long long)iov_len; o.overflow = (o.alloc_size > 0 && o.copyin_len > (unsigned long long)o.alloc_size) ? o.copyin_len - (unsigned long long)o.alloc_size : 0ULL; return o; } static void case_(const char *label, size_t_model iov_len) { struct outcome o = model(iov_len); printf(" %-22s iov_len=0x%016llx -> ioctl_data_size=%ld, alloc=%ld bytes,\n" " fw_sge.length=0x%08x, copyin=%llu bytes\n" " => heap OVERFLOW = %llu bytes %s\n", label, (unsigned long long)iov_len, (long)(int)iov_len, o.alloc_size, o.fw_sge_len, o.copyin_len, o.overflow, o.overflow ? "(ATTACKER-SUPPLIED BYTES PAST ALLOC)" : "(no overflow)"); } int main(void) { printf("DF-1919: mrsas_passthru size_t->int truncation in iov_len\n"); printf(" (mrsas_ioctl.c:161 decl, :228 truncation, :241 alloc,\n"); printf(" :255 fw_sge.length, :258 copyin)\n\n"); case_("benign", 0x40ull); case_("truncated-small", 0x100000008ull); /* THE classic: alloc 8, copyin 4GiB+8 */ case_("truncated-page", 0x100001000ull); /* alloc 4096, copyin 4GiB+4096 */ case_("truncated-zero-lo", 0x100000000ull); /* alloc 0 -> tag fails */ case_("truncated-neg", 0x1FFFFFFFFull); /* alloc = -1 -> tag fails */ case_("truncated-large", 0x200000800ull); /* alloc 2048, copyin 8GiB+2048 */ printf("\n Confirmed: every truncated-small / truncated-page / truncated-large\n"); printf(" case allocates a small DMA buffer (the int-low-32 of iov_len) and then\n"); printf(" issues copyin with the ORIGINAL 64-bit iov_len -> deterministic kernel\n"); printf(" heap overflow with attacker-supplied bytes from mmap'd user memory.\n"); return 0; } |