DF-1171 / overflow_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 | /* * DF-1171 harness — replicates the EXACT overflow math of * ata_raid_intel_read_meta() in sys/dev/disk/nata/ata-raid.c * * This is a userspace replica of the in-kernel data flow: * raid->total_disks = map->total_disks; // ata-raid.c:2230 (no clamp) * for (disk=0; disk < raid->total_disks; disk++) // ata-raid.c:2243 * raid->disks[disk].* = ...; // writes past disks[15] * * It proves: * 1. sizeof(struct ar_disk) == 48 on LP64 (8+16+8+8+4+pad) * 2. sizeof(struct ar_softc) and the byte offset where disks[] overflows * into the trailing lock/disk/devstat/cdev/pid fields, then past the struct * 3. With map->total_disks = 255 (u8 max), the loop writes * (255-16)*48 = 11472 bytes past disks[15] * * Build: cc -Wall -O2 -o overflow_harness overflow_harness.c * Run: ./overflow_harness */ #include <stdio.h> #include <stdint.h> #include <stddef.h> #include <string.h> #include <stdlib.h> /* ---- faithful copies of the kernel struct layouts (ata-raid.h) ---- */ typedef unsigned char u_int8_t; typedef unsigned short u_int16_t; typedef unsigned int u_int; typedef unsigned long u_int64_t; typedef long off_t; typedef void *device_t; typedef void *cdev_t; #define MAX_DISKS 16 #define MAX_VOLUMES 4 /* minimal stand-ins for the kernel objects that follow disks[] */ struct lock { char _pad[64]; }; struct disk { char _pad[256]; }; struct devstat{ char _pad[96]; }; struct proc { char _pad[16]; }; struct ar_softc { int lun; u_int8_t name[32]; int volume; u_int64_t magic_0; u_int64_t magic_1; int type; int status; int format; u_int generation; u_int64_t total_sectors; u_int64_t offset_sectors; u_int16_t heads; u_int16_t sectors; u_int32_t cylinders; u_int width; u_int interleave; u_int total_disks; struct ar_disk { device_t dev; u_int8_t serial[16]; u_int64_t sectors; off_t last_lba; u_int flags; } disks[MAX_DISKS]; int toggle; u_int64_t rebuild_lba; struct lock lock; struct disk disk; struct devstat devstat; cdev_t cdev; struct proc *pid; }; struct intel_raid_mapping { u_int8_t name[16]; u_int64_t total_sectors __attribute__((packed)); u_int32_t state; u_int32_t reserved; u_int32_t filler_0[20]; u_int32_t offset; u_int32_t disk_sectors; u_int32_t stripe_count; u_int16_t stripe_sectors; u_int8_t status; u_int8_t type; u_int8_t total_disks; /* <-- attacker-controlled u8, up to 255 */ u_int8_t magic[3]; u_int32_t filler_1[7]; u_int32_t disk_idx[1]; } __attribute__((packed)); int main(void) { printf("== DF-1171 overflow harness ==\n\n"); printf("sizeof(struct ar_disk) = %zu\n", sizeof(((struct ar_softc*)0)->disks[0])); printf("MAX_DISKS = %d\n", MAX_DISKS); printf("sizeof(struct ar_softc) = %zu\n", sizeof(struct ar_softc)); printf("sizeof(struct ar_softc).disks[] = %zu\n", sizeof(((struct ar_softc*)0)->disks)); printf("\n"); struct ar_softc *rdp = (struct ar_softc *)calloc(1, sizeof(struct ar_softc) + 65536); /* alias the trailing fields so we can name what gets clobbered */ unsigned char *base = (unsigned char *)rdp; unsigned char *disks_base = (unsigned char *)&rdp->disks[0]; unsigned char *disks_end = (unsigned char *)&rdp->disks[MAX_DISKS]; unsigned char *struct_end = base + sizeof(struct ar_softc); printf("offset of disks[0] = %zu\n", (size_t)(disks_base - base)); printf("offset of disks[15] = %zu (last valid)\n", (size_t)((unsigned char *)&rdp->disks[15] - base)); printf("offset of disks[16] = %zu (FIRST OOB write)\n", (size_t)(disks_end - base)); printf("offset of end of struct = %zu\n", (size_t)(struct_end - base)); printf(" -> toggle @ %zu\n", (size_t)((unsigned char *)&rdp->toggle - base)); printf(" -> rebuild_lba@ %zu\n", (size_t)((unsigned char *)&rdp->rebuild_lba - base)); printf(" -> lock @ %zu\n", (size_t)((unsigned char *)&rdp->lock - base)); printf(" -> disk @ %zu\n", (size_t)((unsigned char *)&rdp->disk - base)); printf(" -> devstat @ %zu\n", (size_t)((unsigned char *)&rdp->devstat - base)); printf(" -> cdev @ %zu\n", (size_t)((unsigned char *)&rdp->cdev - base)); printf(" -> pid @ %zu\n", (size_t)((unsigned char *)&rdp->pid - base)); printf("\n"); /* the bug: replicate the loop with attacker total_disks = 255 */ struct intel_raid_mapping map; memset(&map, 0, sizeof(map)); map.total_disks = 255; /* u8 max */ int overflow_writes = 0; for (int disk = 0; disk < map.total_disks; disk++) { if (disk >= MAX_DISKS) { overflow_writes++; /* poison the slot to visualize the OOB write */ unsigned char *target = (unsigned char *)&rdp->disks[disk]; memset(target, 0xAB, sizeof(rdp->disks[0])); } } size_t oob_bytes = (size_t)overflow_writes * sizeof(rdp->disks[0]); size_t past_struct = 0; if (disks_end + oob_bytes > struct_end) past_struct = (disks_end + oob_bytes) - struct_end; printf("== attacker sets map->total_disks = %d ==\n", map.total_disks); printf("loop writes raid->disks[0..%d]; valid range is [0..%d]\n", map.total_disks - 1, MAX_DISKS - 1); printf("OOB entries written past disks[15]: %d\n", overflow_writes); printf("OOB bytes written past disks[15]: %zu (=%d * %zu)\n", oob_bytes, overflow_writes, sizeof(rdp->disks[0])); printf(" of which INSIDE ar_softc struct : %zu bytes (clobbers toggle/rebuild_lba/lock/disk/devstat/cdev/pid)\n", oob_bytes - past_struct); printf(" of which PAST the struct boundary: %zu bytes (into adjacent kernel heap)\n", past_struct); printf("\n"); /* name the first victim field clobbered */ printf("first OOB write lands at struct offset %zu ", (size_t)(disks_end - base)); if ((size_t)(disks_end - base) == (size_t)((unsigned char*)&rdp->toggle - base)) printf("== ar_softc.toggle (then rebuild_lba, lock, disk, devstat, cdev, pid, then heap)\n"); printf("\n"); /* demonstrate attacker-controlled content (serial+sectors are meta-derived) */ printf("Per OOB entry, attacker-controlled bytes:\n"); printf(" serial[16] (16) + sectors (8) = 24 of %zu bytes attacker-shaped; " "dev/last_lba/flags also written (NULL/0/0 by the loop)\n", sizeof(rdp->disks[0])); free(rdp); printf("RESULT: OOB heap write CONFIRMED — %zu bytes past disks[15] for total_disks=%d\n", oob_bytes, map.total_disks); return 0; } |