DF-4238 / fwcheck.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 | /* * fwcheck.c - DF-4238 evidence harness * * Replicates, verbatim in semantics, the DragonFly amdgpu firmware-parsing * sequence exercised by sdma_v3_0_init_microcode() (sys/dev/drm/amd/amdgpu/ * sdma_v3_0.c:309-326) on a firmware blob: * * 1. request_firmware() hands the driver a kernel buffer of fw->datasize * bytes (here: 4 bytes placed at the very END of a mapped page, with an * unmapped guard page right after - standing in for the exact-size * kernel allocation). * 2. amdgpu_ucode_validate(fw) (amdgpu_ucode.c:251-260) accepts the blob * iff fw->datasize == le32(hdr->size_bytes). A 4-byte blob declaring * size_bytes = 4 PASSES - there is no minimum-size or cross-field * validation (root finding DF-1946). * 3. sdma_v3_0.c then reads: * hdr->header.ucode_version (common_firmware_header + 16) * hdr->ucode_feature_version (sdma v1_0 header + 32) * header->ucode_size_bytes (common header + 24) * and folds ucode_size_bytes into adev->firmware.fw_size. * * With datasize == 4 every one of those reads runs past the end of the * buffer; against the guard page the first read (offset 16) faults with * SIGSEGV - proof of the out-of-bounds access without needing ASan. * Struct layouts mirror amdgpu_ucode.h:26-37 and :120-126. * * Threat model: the firmware file is root-installed (DragonFly amdgpufw_* * module firmware), which is why DF-1598/DF-1553/DF-1451 are Low. This * harness proves the parsing primitive only - the kernel path itself is * hardware-gated (Tonga/Polaris/Fiji/Carrizo/Stoney SDMA engine). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <sys/mman.h> #include <unistd.h> /* make the proof readable even when we die on the guard page */ #define P(...) do { printf(__VA_ARGS__); fflush(stdout); } while (0) struct common_firmware_header { /* amdgpu_ucode.h:26-37 */ uint32_t size_bytes; uint32_t header_size_bytes; uint16_t header_version_major, header_version_minor; uint16_t ip_version_major, ip_version_minor; uint32_t ucode_version; /* offset 16 */ uint32_t ucode_size_bytes; /* offset 24 */ uint32_t ucode_array_offset_bytes; uint32_t crc32; }; struct sdma_firmware_header_v1_0 { /* amdgpu_ucode.h:120-126 */ struct common_firmware_header header; uint32_t ucode_feature_version; /* offset 32 */ uint32_t ucode_change_version; uint32_t jt_offset; uint32_t jt_size; }; struct firmware { /* the fields sdma_v3_0.c uses */ const uint8_t *data; size_t datasize; }; /* amdgpu_ucode.c:251-260, verbatim (little-endian host) */ static int amdgpu_ucode_validate(const struct firmware *fw) { const struct common_firmware_header *hdr = (const struct common_firmware_header *)fw->data; if (fw->datasize == hdr->size_bytes) return 0; return -1; } int main(void) { long pg = sysconf(_SC_PAGESIZE); uint8_t *map = mmap(NULL, pg * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (map == MAP_FAILED) { perror("mmap"); return 1; } if (mprotect(map + pg, pg, PROT_NONE) != 0) { perror("mprotect"); return 1; } /* 4-byte firmware blob at the very end of the mapped page */ uint8_t *blob = map + pg - 4; uint32_t four = 4; memcpy(blob, &four, 4); /* hdr->size_bytes = 4 */ struct firmware fw = { blob, 4 }; int rc = amdgpu_ucode_validate(&fw); P("amdgpu_ucode_validate(4-byte blob) = %d (0 == accepted)\n", rc); if (rc != 0) { P("VALIDATE REJECTED - harness premise broken\n"); return 1; } P("validate passed: no minimum-size check (DF-1946 root), " "sdma_v3_0.c proceeds to read sub-header fields\n"); /* sdma_v3_0.c:315-317 */ const struct sdma_firmware_header_v1_0 *hdr = (const struct sdma_firmware_header_v1_0 *)fw.data; uint32_t fw_version = hdr->header.ucode_version; /* OOB read @+16 */ P("survived ucode_version read (page not guarded there?)\n"); uint32_t feature_version = hdr->ucode_feature_version; /* OOB read @+32 */ P("survived ucode_feature_version read\n"); /* sdma_v3_0.c:324-326 */ uint32_t fw_size_add = (hdr->header.ucode_size_bytes + 4095) & ~4095u; P("kernel consumed: fw_version=%08x feature_version=%08x " "fw_size += %u\n", fw_version, feature_version, fw_size_add); P("NOT REACHED: one of the reads above faults into the guard " "page == heap out-of-bounds read in the kernel equivalent\n"); return 0; } |