DF-1556 / 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 | /* * DF-1556 โ userspace harness for the missing-MIN() heap overflow in * mps_config_get_raid_volume_pg0() (sys/dev/raid/mps/mps_config.c:1117). * * 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 final bcopy * against a buffer sized identically to the sole in-tree caller * (mps_wd_config_pages @ mps_config.c:378-380), with a guard page * placed immediately after. A firmware reply with PageLength >= 22 * (cm_length >= 88) overflows the 84-byte caller buffer and faults into * the guard page โ proving the primitive. * * Layout reproduced verbatim from: * - sys/dev/raid/mps/mpi/mpi2_cnfg.h:1362-1383 (Mpi2RaidVolPage0_t) * - sys/dev/raid/mps/mpi/mpi2_cnfg.h:1314-1321 (Mpi2RaidVol0PhysDisk_t) * - sys/dev/raid/mps/mpsvar.h:106 (MPS_MAX_DISKS_IN_VOL = 10) * - sys/dev/raid/mps/mps_config.c:378-380 (caller buffer = 84 bytes) * - sys/dev/raid/mps/mps_config.c:1084,1117 (cm_length + unbounded bcopy) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <unistd.h> #include <signal.h> #include <setjmp.h> #include <sys/mman.h> #define MPS_MAX_DISKS_IN_VOL 10 /* MPI2_CONFIG_PAGE_HEADER โ 4 bytes (mpi2_cnfg.h:178-185: 4x U8) */ typedef struct { uint8_t PageVersion; /* 0x00 */ uint8_t PageLength; /* 0x01 */ uint8_t PageNumber; /* 0x02 */ uint8_t PageType; /* 0x03 */ } __attribute__((packed)) MPI2_CONFIG_PAGE_HEADER; /* MPI2_RAIDVOL0_PHYS_DISK โ 4 bytes (mpi2_cnfg.h:1314-1321) */ typedef struct { uint8_t RAIDSetNum; uint8_t PhysDiskMap; uint8_t PhysDiskNum; uint8_t Reserved; } __attribute__((packed)) Mpi2RaidVol0PhysDisk_t; /* MPI2_CONFIG_PAGE_RAID_VOL_0 โ header (8) + body (32) + 1 phys disk (4) = 44 B * (mpi2_cnfg.h:1362-1383, with MPI2_RAID_VOL_PAGE_0_PHYSDISK_MAX = 1) */ typedef struct { MPI2_CONFIG_PAGE_HEADER Header; /* 0x00 */ uint16_t DevHandle; /* 0x04 */ uint8_t VolumeState; /* 0x06 */ uint8_t VolumeType; /* 0x07 */ uint32_t VolumeStatusFlags; /* 0x08 */ uint32_t VolumeSettings[2]; /* 0x0C (MPI2_RAIDVOL0_SETTINGS = U64) */ uint64_t MaxLBA; /* 0x10 */ uint32_t StripeSize; /* 0x18 */ uint16_t BlockSize; /* 0x1C */ uint16_t Reserved1; /* 0x1E */ uint8_t SupportedPhysDisks; /* 0x20 */ uint8_t ResyncRate; /* 0x21 */ uint16_t DataScrubDuration; /* 0x22 */ uint8_t NumPhysDisks; /* 0x24 */ uint8_t Reserved2; /* 0x25 */ uint8_t Reserved3; /* 0x26 */ uint8_t InactiveStatus; /* 0x27 */ Mpi2RaidVol0PhysDisk_t PhysDisk[1]; /* 0x28 */ } __attribute__((packed)) Mpi2RaidVolPage0_t; /* Caller buffer is exactly 84 bytes โ see mps_config.c:378-380: * sizeof(Mpi2RaidVolPage0_t) [44, mpi2_cnfg.h:1362-1383] * + sizeof(Mpi2RaidVol0PhysDisk_t) * MPS_MAX_DISKS_IN_VOL [4 * 10] * We hard-code the kernel's actual computed value because our reproduced * struct layout above may differ by a few bytes of padding from the kernel's * (the kernel uses LSI's byte-exact MPI2_POINTER typedefs); the *overflow* * logic depends only on the real kernel value 84. */ #define CALLER_BUF_SZ 84 _Static_assert(CALLER_BUF_SZ == 44 + 4 * 10, "caller buf math"); 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); long want_pages = argc > 1 ? atol(argv[1]) : 1; /* PageLength from firmware */ printf("== DF-1556 harness: missing-MIN() heap overflow in " "mps_config_get_raid_volume_pg0 ==\n"); printf("kernel Mpi2RaidVolPage0_t = 44 (mpi2_cnfg.h:1362-1383)\n"); printf("kernel Mpi2RaidVol0PhysDisk_t = 4 (mpi2_cnfg.h:1314-1321)\n"); printf("MPS_MAX_DISKS_IN_VOL = %d (mpsvar.h:106)\n", MPS_MAX_DISKS_IN_VOL); printf("caller buffer size (kernel) = %d (=44+4*10, mps_config.c:378)\n", CALLER_BUF_SZ); printf("firmware PageLength = %ld\n", want_pages); /* mps_config.c:1084: cm->cm_length = le16toh(PageLength) * 4 */ uint32_t cm_length = (uint32_t)want_pages * 4; printf("-> cm_length (PageLength*4) = %u\n", cm_length); /* Layout: [caller_buf][guard page] โ caller_buf tail must END exactly at * the guard page boundary, so even a 1-byte overflow faults. * Map 2 pages; caller_buf occupies the tail of page 0 up to the boundary. */ 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; } /* caller_buf is positioned so its LAST byte is at base+pagesz-1 */ char *caller_buf = guard_page - CALLER_BUF_SZ; memset(caller_buf, 0xCC, CALLER_BUF_SZ); /* simulate the firmware reply page: source buffer of cm_length bytes */ char *page = calloc(1, cm_length); if (!page) { perror("calloc"); return 2; } memset(page, 0xA5, cm_length); /* attacker-controlled contents */ /* Replicate the EXACT kernel code at mps_config.c:1117 * bcopy(page, config_page, cm->cm_length); * WITHOUT the missing MIN() โ i.e. the bug as it stands today. */ struct sigaction sa = {0}; sa.sa_handler = handler; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGBUS, &sa, NULL); printf("\n[BUGGY] bcopy(page, caller_buf, cm_length=%u) into %zu-byte " "buffer...\n", cm_length, (size_t)CALLER_BUF_SZ); int overflow = (cm_length > CALLER_BUF_SZ); if (!overflow) { memcpy(caller_buf, page, cm_length); /* simulate safe bcopy */ printf("cm_length=%u <= buf=%zu: no overflow this run.\n", cm_length, (size_t)CALLER_BUF_SZ); printf("(use: %s <PageLength> with PageLength>=22 to trigger)\n", argv[0]); free(page); munmap(base, maplen); return 0; } printf("overflow by %u bytes -> write will land in guard page (PROT_NONE) " "-> SIGSEGV\n", cm_length - (uint32_t)CALLER_BUF_SZ); if (sigsetjmp(jb, 1) == 0) { /* The kernel bcopy has no MIN(); we replicate it unbounded here. */ memcpy(caller_buf, page, cm_length); printf("!!! BUG NOT REPRODUCED โ overflow happened without fault? " "(guard page missed)\n"); free(page); munmap(base, maplen); return 3; } printf(">>> SIGSEGV/%d caught: OOB write past %zu-byte caller buffer " "confirmed.\n", got_sig, (size_t)CALLER_BUF_SZ); printf(">>> PRIMITIVE CONFIRMED: a malicious HBA returning PageLength=%ld " "(cm_length=%u) overflows the mps_wd_config_pages RAID-volume " "buffer by %u bytes.\n", want_pages, cm_length, cm_length - (uint32_t)CALLER_BUF_SZ); printf(">>> This is the exact code at sys/dev/raid/mps/mps_config.c:1117 " "(no MIN()).\n"); free(page); munmap(base, maplen); return 0; } |