DF-1130 / 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 | /* * DF-1130 harness: radeon/si.c si_mc_load_microcode / si_cp_load_microcode / * si_rlc_resume OOB heap read via unchecked firmware * header offset/size fields. * * Userspace logic harness. radeon_ucode_validate (radeon_ucode.c:156-165) only * checks fw->datasize == hdr->size_bytes; it does NOT validate * ucode_array_offset_bytes + ucode_size_bytes <= datasize nor * io_debug_array_offset_bytes + io_debug_size_bytes <= datasize. So a crafted * firmware blob whose header's size_bytes matches the file size, but whose * ucode_array_offset_bytes points near the end, makes the loader read past the * firmware buffer into adjacent kernel heap. * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdint.h> #include <string.h> #include <stdlib.h> struct common_firmware_header { uint32_t size_bytes; /* whole-file size including header */ uint32_t ucode_array_offset_bytes; /* offset of ucode payload */ uint32_t ucode_size_bytes; /* ucode payload length */ uint8_t pad[64 - 12]; }; struct mc_firmware_header { struct common_firmware_header header; uint32_t io_debug_array_offset_bytes; uint32_t io_debug_size_bytes; }; /* Mirror of radeon_ucode_validate (radeon_ucode.c:156-165) -- only checks total size */ static int radeon_ucode_validate_buggy(uint8_t *fw, uint32_t datasize) { struct common_firmware_header *hdr = (struct common_firmware_header *)fw; if (datasize == hdr->size_bytes) return 0; /* passes -- but the offset/size are unchecked */ return -1; } /* Mirror of si_mc_load_microcode (si.c:1574-1584) OOB calculation */ static int si_mc_load_oob_range(uint8_t *fw, uint32_t datasize, uint32_t *uc_off, uint32_t *uc_size, uint32_t *io_off, uint32_t *io_size) { struct mc_firmware_header *hdr = (struct mc_firmware_header *)fw; /* buggy: NO check against datasize */ *uc_off = hdr->header.ucode_array_offset_bytes; *uc_size = hdr->header.ucode_size_bytes; *io_off = hdr->io_debug_array_offset_bytes; *io_size = hdr->io_debug_size_bytes; return 0; } /* Fixed loader -- validates each region against datasize */ static int si_mc_load_checked(uint8_t *fw, uint32_t datasize, uint32_t *uc_off, uint32_t *uc_size, uint32_t *io_off, uint32_t *io_size) { struct mc_firmware_header *hdr = (struct mc_firmware_header *)fw; *uc_off = hdr->header.ucode_array_offset_bytes; *uc_size = hdr->header.ucode_size_bytes; *io_off = hdr->io_debug_array_offset_bytes; *io_size = hdr->io_debug_size_bytes; if (*uc_off > datasize || *uc_size > datasize - *uc_off) return -1; if (*io_off > datasize || *io_size > datasize - *io_off) return -1; return 0; } int main(void) { /* Build a "crafted" firmware blob: * - 1024 bytes total (so size_bytes == datasize passes validate) * - ucode_array_offset_bytes = 0xff0 (4080), ucode_size_bytes = 0x4000 (16384) * - For loops read 16384/4 = 4096 dwords starting at offset 4080. * The first ~16 dwords are in-bounds; the next 4080 are OOB. */ enum { DS = 1024 }; uint8_t *fw = malloc(DS + 65536); memset(fw, 0xCC, DS + 65536); /* OOB region reads back as 0xCC */ struct mc_firmware_header *hdr = (struct mc_firmware_header *)fw; hdr->header.size_bytes = DS; hdr->header.ucode_array_offset_bytes = 0xff0; /* near end of file */ hdr->header.ucode_size_bytes = 0x4000; /* but size is HUGE */ hdr->io_debug_array_offset_bytes = 0; hdr->io_debug_size_bytes = 0; printf("=== DF-1130 harness: crafted SI firmware (datasize=%u) ===\n\n", DS); printf(" header.ucode_array_offset_bytes = 0x%x\n", hdr->header.ucode_array_offset_bytes); printf(" header.ucode_size_bytes = 0x%x\n", hdr->header.ucode_size_bytes); printf(" => end of payload = 0x%x (vs datasize=0x%x) -> OOB by %u bytes\n\n", hdr->header.ucode_array_offset_bytes + hdr->header.ucode_size_bytes, DS, hdr->header.ucode_array_offset_bytes + hdr->header.ucode_size_bytes - DS); printf("[A] radeon_ucode_validate (radeon_ucode.c:156-165):\n"); int rc = radeon_ucode_validate_buggy(fw, DS); printf(" rc=%d (PASSES -- only checks size_bytes==datasize)\n\n", rc); uint32_t uc_off, uc_size, io_off, io_size; printf("[B] si_mc_load_microcode buggy (si.c:1574-1584):\n"); si_mc_load_oob_range(fw, DS, &uc_off, &uc_size, &io_off, &io_size); uint32_t end = uc_off + uc_size; uint32_t oob = (end > DS) ? (end - DS) : 0; printf(" would loop reading u32 from fw+0x%x for %u iters\n", uc_off, uc_size / 4); printf(" >>> In kernel: reads %u bytes past firmware buffer into adjacent heap <<<\n\n", oob); printf("[C] si_mc_load_microcode fixed (fix.diff):\n"); rc = si_mc_load_checked(fw, DS, &uc_off, &uc_size, &io_off, &io_size); printf(" rc=%d (rejected: ucode_array_offset_bytes + ucode_size_bytes > datasize)\n", rc); free(fw); return 0; } |