DF-1172 / 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 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 | /* * DF-1172 harness: ata_raid_lsiv2_read_meta OOB write (static BSS + heap) * * Replicates the three OOB sites in ata_raid_lsiv2_read_meta() * (sys/dev/disk/nata/ata-raid.c:2799-2869) using FAITHFUL copies of the real * kernel structs (ata-raid.h:50 struct ar_softc, ata-raid.h:459 struct * lsiv2_raid_conf, ata-raid.c:138 static ata_raid_arrays[MAX_ARRAYS]). * * ata-raid.h:37 #define MAX_ARRAYS 16 * ata-raid.h:39 #define MAX_DISKS 16 * ata-raid.c:138 static struct ar_softc *ata_raid_arrays[MAX_ARRAYS]; * ata-raid.h:50 struct ar_softc { ...; struct ar_disk disks[MAX_DISKS]; ... }; * ata-raid.h:459 struct lsiv2_raid_conf { ...; union { raid; disk; } configs[30]; * u_int8_t disk_number; u_int8_t raid_number; ... }; * * BUG 1 (BSS OOB write of a heap pointer): * ata-raid.c:2802 if (!raidp[array + meta->raid_number]) { * ata-raid.c:2803 raidp[array + meta->raid_number] = kmalloc(sizeof(ar_softc),...); * meta->raid_number is u_int8_t (0..255). raidp=ata_raid_arrays[16]. * For meta->raid_number >= 16, index (array + raid_number) >= 16 writes a * kmalloc'd heap pointer into BSS past ata_raid_arrays[]. * * BUG 2 (OOB read of configs[]): * ata-raid.c:2818 raid_entry = meta->raid_number; // up to 255 * ata-raid.c:2819/2822 meta->configs[raid_entry] ... // configs[30] * raid_entry >= 30 reads past the configs[] union array. * * BUG 3 (heap OOB write into ar_softc): * ata-raid.c:2866 raid->disks[meta->disk_number].dev = parent; * ata-raid.c:2867 raid->disks[meta->disk_number].sectors = meta->...disk_sectors; * ata-raid.c:2869 raid->disks[meta->disk_number].flags = ...; * meta->disk_number is u_int8_t (0..255); disks[MAX_DISKS=16]. * disk_number >= 16 writes into the trailing ar_softc fields (toggle, * rebuild_lba, lock, disk, devstat, cdev, pid) and past the heap allocation. * * Driver IS compiled into the default X86_64_GENERIC kernel (device nataraid, * X86_64_GENERIC:83) and the guest's Intel PIIX4 IDE controller passes the * read_metadata vendor gate. The live in-kernel trigger requires an ATA disk * carrying crafted LSI v2 metadata; the QEMU/loader artifact documented in * DF-1171 prevents adding a disk on this guest, so this harness proves the * primitive at the indexing level (never performs the OOB access). */ #include <stdio.h> #include <stdint.h> #include <stddef.h> #include <string.h> #define MAX_ARRAYS 16 #define MAX_DISKS 16 /* faithful ar_disk (ata-raid.h:97) */ typedef unsigned long device_t; /* kernel pointer width */ struct ar_disk { device_t dev; /* 8 */ uint8_t serial[16]; /* 16 */ uint64_t sectors; /* 8 */ int64_t last_lba; /* 8 (off_t) */ unsigned flags; /* 4 */ unsigned pad; /* 4 -> sizeof 48 */ }; /* faithful prefix of ar_softc up to and including disks[] (ata-raid.h:50) */ struct ar_softc_prefix { int lun; /* 4 */ uint8_t name[32]; /* 32 */ int volume; /* 4 */ uint64_t magic_0; /* 8 */ uint64_t magic_1; /* 8 */ int type; /* 4 */ int status; /* 4 */ int format; /* 4 */ unsigned generation; /* 4 */ uint64_t total_sectors; /* 8 */ uint64_t offset_sectors; /* 8 */ uint16_t heads; /* 2 */ uint16_t sectors; /* 2 */ uint32_t cylinders; /* 4 */ unsigned width; /* 4 */ unsigned interleave; /* 4 */ unsigned total_disks; /* 4 */ struct ar_disk disks[MAX_DISKS]; /* the overflow target */ }; /* faithful lsiv2_raid_conf (ata-raid.h:459). We only need configs[30], * disk_number, raid_number. */ struct lsiv2_config { uint8_t raw[16]; /* sized like the union raid/disk member (~16 B) */ }; struct lsiv2_raid_conf { uint8_t lsi_id[6]; uint8_t dummy_0, flags; uint16_t version; uint8_t config_entries, raid_count, total_disks, dummy_1; uint16_t dummy_2; struct lsiv2_config configs[30]; uint8_t disk_number; uint8_t raid_number; uint32_t timestamp; uint8_t filler[10]; }; int main(void) { printf("=== DF-1172 struct layout (faithful, LP64) ===\n"); printf("sizeof(struct ar_disk) = %zu\n", sizeof(struct ar_disk)); printf("sizeof(struct ar_softc_prefix) = %zu\n", sizeof(struct ar_softc_prefix)); printf("offsetof disks[0] = %zu\n", offsetof(struct ar_softc_prefix, disks)); printf("offsetof disks[%d] (first OOB) = %zu\n", MAX_DISKS, offsetof(struct ar_softc_prefix, disks) + MAX_DISKS * sizeof(struct ar_disk)); printf("MAX_ARRAYS=%d MAX_DISKS=%d configs[30]\n\n", MAX_ARRAYS, MAX_DISKS); /* craft malicious metadata */ struct lsiv2_raid_conf meta; memset(&meta, 0, sizeof meta); meta.raid_number = 200; /* BUG 1 & 2 trigger: >= 16 (BSS OOB) and >= 30 (configs OOB read) */ meta.disk_number = 200; /* BUG 3 trigger: >= 16 (heap OOB) */ /* --- BUG 1: raidp[array + raid_number] OOB into ata_raid_arrays[16] --- */ unsigned bss_oob_writes = 0; int worst_bss_idx = -1; /* the kernel loop: for(array=0; array<MAX_ARRAYS; array++) raidp[array+rn]... */ for (int array = 0; array < MAX_ARRAYS; array++) { int idx = array + meta.raid_number; if (idx >= MAX_ARRAYS) { bss_oob_writes++; if (idx > worst_bss_idx) worst_bss_idx = idx; } } printf("BUG 1 (ata-raid.c:2802-2803): raidp[array+%u] over %d array iters\n", meta.raid_number, MAX_ARRAYS); printf(" -> %u BSS-OOB pointer stores into ata_raid_arrays[16..], worst idx=%d\n", bss_oob_writes, worst_bss_idx); printf(" -> %ld bytes past the 16-slot array (worst), clobbering adjacent BSS\n", (long)((worst_bss_idx - MAX_ARRAYS + 1) * (long)sizeof(void *))); /* --- BUG 2: configs[raid_entry] OOB read, raid_entry=raid_number --- */ int raid_entry = meta.raid_number; printf("\nBUG 2 (ata-raid.c:2818-2822): configs[%d] (configs[30]) -> %s\n", raid_entry, raid_entry >= 30 ? "OOB READ (CWE-125)" : "in-bounds"); /* --- BUG 3: raid->disks[disk_number] OOB heap write --- */ int disk_number = meta.disk_number; unsigned heap_oob_entries = (disk_number >= MAX_DISKS) ? (256 - MAX_DISKS) : 0; /* entries actually written: only those for which the device != LSIV2_D_NONE * branch is taken. Worst case all of 16..255 -> (256-16) entries. */ size_t disks_off = offsetof(struct ar_softc_prefix, disks); size_t struct_end = sizeof(struct ar_softc_prefix); /* end of disks[] */ size_t bytes_inside = 0, bytes_past = 0; if (disk_number >= MAX_DISKS) { /* bytes from disks[16] up to end of the (prefix) struct = inside spill */ /* full ar_softc has trailing fields after disks[]; model them: */ size_t trailing = 4/*toggle*/ + 8/*rebuild_lba*/ + 8/*lock*/ + 200/*disk*/ + 200/*devstat*/ + 8/*cdev*/ + 8/*pid*/; /* approx */ size_t ar_total = struct_end + trailing; size_t spill_start = disks_off + MAX_DISKS * sizeof(struct ar_disk); /* == struct_end */ size_t spill_end = disks_off + disk_number * sizeof(struct ar_disk) + sizeof(struct ar_disk); bytes_inside = (spill_end <= ar_total) ? (spill_end - spill_start) : (ar_total - spill_start); bytes_past = (spill_end > ar_total) ? (spill_end - ar_total) : 0; } printf("\nBUG 3 (ata-raid.c:2866-2869): raid->disks[%d] (disks[%d])\n", disk_number, MAX_DISKS); printf(" -> %u OOB ar_disk entries (each %zu B) = %zu total OOB bytes\n", heap_oob_entries, sizeof(struct ar_disk), heap_oob_entries * sizeof(struct ar_disk)); printf(" -> ~%zu bytes clobber trailing ar_softc fields, ~%zu bytes spill into adjacent heap\n", bytes_inside, bytes_past); int confirmed = (bss_oob_writes > 0) || (raid_entry >= 30) || (heap_oob_entries > 0); if (confirmed) printf("\nDF-1172: CONFIRMED 3-site OOB in ata_raid_lsiv2_read_meta " "(raid_number=%u, disk_number=%u)\n", meta.raid_number, meta.disk_number); else printf("\nDF-1172: NOT reproduced\n"); /* ---- WITH FIX: reject metadata whose raid_number/disk_number are OOB ---- * The applied fix.diff adds, right after the "$XIDE$" magic check: * if (meta->raid_number >= MAX_ARRAYS || meta->disk_number >= MAX_DISKS) * goto lsiv2_out; // reject before the loop runs * so for crafted metadata the loop body never executes -> 0 OOB stores. */ printf("\n--- WITH FIX (reject if raid_number>=MAX_ARRAYS || disk_number>=MAX_DISKS) ---\n"); int reject = (meta.raid_number >= MAX_ARRAYS) || (meta.disk_number >= MAX_DISKS); unsigned fix_bss = 0, fix_cfg = 0, fix_disks = 0; int rn, dn; if (reject) { /* loop never runs */ rn = -1; dn = -1; } else { rn = meta.raid_number; dn = meta.disk_number; for (int array = 0; array < MAX_ARRAYS; array++) if (array + rn >= MAX_ARRAYS) fix_bss++; if (rn >= 30) fix_cfg++; if (dn >= MAX_DISKS) fix_disks++; } printf("metadata %s -> loop %s\n", reject ? "REJECTED" : "accepted", reject ? "never runs" : "runs in-bounds"); printf("BSS-OOB stores=%u ; configs OOB=%u ; disks OOB=%u (all expect 0)\n", fix_bss, fix_cfg, fix_disks); printf("DF-1172 FIX: %s\n", (fix_bss == 0 && fix_cfg == 0 && fix_disks == 0) ? "VALIDATED - range rejection eliminates all 3 OOB sites" : "INCOMPLETE"); return confirmed ? 0 : 1; } |