DF-1917 / 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 | /* * DF-1917 source-confirmation harness โ mrsas_passthru unbounded sgl_off * yields an OOB SGE write past the 1024-byte cmd->frame DMA allocation. * * sys/dev/raid/mrsas/mrsas_ioctl.c:219-220 * kern_sge32 = (struct mrsas_sge32 *) * ((unsigned long)cmd->frame + user_ioc->sgl_off); * * sys/dev/raid/mrsas/mrsas_ioctl.c:225-255 * for (i = 0; i < user_ioc->sge_count; i++) { * ... * kern_sge32[i].phys_addr = (u_int32_t)ioctl_data_phys_addr[i]; * kern_sge32[i].length = user_ioc->sgl[i].iov_len; * } * * user_ioc->sgl_off is a u_int32_t (mrsas_ioctl.h:83) taken verbatim from * userspace and NEVER validated against MRSAS_MFI_FRAME_SIZE (1024, * mrsas.h:876). Each SGE writes 8 bytes (mrsas_sge32 { u32 phys_addr; u32 * length; }, mrsas.h:1883-1886, #pragma pack(1)) at frame+sgl_off+8*i. * With sgl_off=1016 and sge_count=1, the 8-byte SGE occupies bytes * 1016..1023 โ right at the edge. sgl_off=1020 spills 4 bytes past the * allocation; sgl_off>=1024 spills entirely. sgl_off=0xFFFFFFFF wraps the * pointer to cmd->frame - 1, an arbitrary-offset 8-byte write into kernel * heap relative to the DMA allocation. MAX_IOCTL_SGE=16 (mrsas_ioctl.h:66) * multiplies the primitive: a single call can land up to 16*8 = 128 bytes * of attacker-offset writes. * * Why a harness: /dev/mrsas0 is created only when an LSI MegaRAID SAS HBA * is detected by the PCI probe (mrsas_attach, mrsas.c:790-792 make_dev). * The QEMU audit guest has no such HBA (verified: pciconf -lv lists none; * /dev/mrsas* absent), so mrsas_passthru is unreachable at runtime. * Additionally the node is created mode 0660 root:operator (mrsas.c:790), * and maxx (uid 1001) is not in operator (verified) โ so even on a host * with the HBA, the unprivileged ceiling is "operator-group member". The * harness reproduces the verbatim buggy arithmetic against a 1024-byte * model of cmd->frame. * * 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 */ #define MAX_IOCTL_SGE 16 /* mrsas_ioctl.h:66 */ struct mrsas_sge32 { /* mrsas.h:1883-1886 (#pragma pack(1)) */ uint32_t phys_addr; uint32_t length; }; /* Verbatim mrsas_ioctl.c:219-255 control flow, model cmd->frame as a * 1024-byte heap allocation. */ static unsigned int oob_count(const uint32_t sgl_off, const uint32_t sge_count) { unsigned char *frame = calloc(1, MRSAS_MFI_FRAME_SIZE + 256); /* +spill pad */ unsigned char *base = frame; unsigned int oob = 0; /* kern_sge32 = (struct mrsas_sge32 *)((unsigned long)cmd->frame + sgl_off) */ struct mrsas_sge32 *kern_sge32 = (struct mrsas_sge32 *)(base + sgl_off); for (unsigned int i = 0; i < sge_count; i++) { /* the two unchecked stores at mrsas_ioctl.c:254-255 */ unsigned char *pa = (unsigned char *)&kern_sge32[i].phys_addr; unsigned char *le = (unsigned char *)&kern_sge32[i].length; for (unsigned int b = 0; b < sizeof(uint32_t); b++) { if (pa - base + b >= MRSAS_MFI_FRAME_SIZE) oob++; if (le - base + b >= MRSAS_MFI_FRAME_SIZE) oob++; } } free(frame); return oob; } static void case_(const char *label, uint32_t sgl_off, uint32_t sge_count) { unsigned int oob = oob_count(sgl_off, sge_count); printf(" sgl_off=0x%08x sge_count=%2u -> %u OOB bytes %s\n", sgl_off, sge_count, oob, oob ? "(OOB WRITE past 1024-byte frame)" : "(in-bounds)"); (void)label; } int main(void) { printf("DF-1917: mrsas_passthru unbounded sgl_off (mrsas_ioctl.c:219-255)\n"); printf(" cmd->frame is bus_dmamem_alloc'd at MRSAS_MFI_FRAME_SIZE=1024\n"); printf(" bytes (mrsas.c:451-462 mrsas_create_frame_pool). sgl_off is a\n"); printf(" u32 from userspace (mrsas_ioctl.h:83), never validated.\n\n"); /* In-bounds reference: sgl_off=128, sge_count=1, occupies 128..135 */ case_("in-bounds", 128u, 1); /* Edge case: last byte written is byte 1023 */ case_("edge", MRSAS_MFI_FRAME_SIZE - 8u, 1); /* Minimal OOB: 4 bytes past end */ case_("oob-4", MRSAS_MFI_FRAME_SIZE - 4u, 1); /* Fully OOB single SGE */ case_("oob-full", MRSAS_MFI_FRAME_SIZE, 1); /* Max spill: 16 SGEs starting at offset 1020 -> 4 + 15*8 = 124 bytes OOB */ case_("oob-max", MRSAS_MFI_FRAME_SIZE - 4u, MAX_IOCTL_SGE); /* Pointer-wrap: same arithmetic as kernel ((unsigned long)base + 0xFFFFFFFF). * On amd64 this is base + 4GiB - 1, an arbitrary-offset write into kernel * heap relative to the DMA allocation. */ case_("wrap", 0xFFFFFFFFu, 1); printf("\n Confirmed: every case with OOB bytes > 0 writes attacker-supplied\n"); printf(" phys_addr/length past the cmd->frame DMA allocation.\n"); return 0; } |