DF-1177 / 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 | /* * DF-1177 — userspace harness for rv_init_cp_jump_table jt_size OOB + mec2_fw NULL. * * Reconstructs the cp_table-write loop from * sys/dev/drm/amd/amdgpu/gfx_v9_0.c:1068-1128 * * Three bugs: * (1) jt_size from each firmware header (CE/PFP/ME/MEC/MEC2) is used as the * loop bound for writes into the cp_table BO without any bound check * against the cp_table_size (16896 dwords). A large jt_size -> OOB * write past the BO mapping. * (2) jt_offset + jt_size used as the read bound into the firmware buffer * without validation against fw->datasize -> OOB read past firmware. * (3) me==4 dereferences adev->gfx.mec2_fw->data unconditionally; mec2_fw * is set to NULL in init_microcode when the (optional) mec2 firmware * fails to load (gfx_v9_0.c:744) -> immediate NULL deref panic. * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <signal.h> #include <setjmp.h> #include <sys/types.h> /* no-op stand-in so the harness compiles without kernel macros */ #define le32_to_cpu_unused(x) (x) /* Mirror the on-disk firmware header subset that the parser reads. */ struct common_firmware_header { uint32_t ucode_array_offset_bytes; /* not used here directly */ uint32_t ucode_version; uint32_t size_bytes; /* ... */ }; struct gfx_firmware_header_v1_0 { struct common_firmware_header header; uint32_t ucode_feature_version; uint32_t jt_offset; uint32_t jt_size; } __attribute__((packed)); struct firmware { const uint8_t *data; size_t datasize; }; struct amdgpu_gfx { struct firmware *ce_fw, *pfp_fw, *me_fw, *mec_fw, *mec2_fw; }; #define CP_TABLE_DWORDS 16896 /* ALIGN(96*5*4, 2048)/4 + (64*1024)/4 = 512 + 16384 */ #define MAX_ME 5 static sigjmp_buf jb; static volatile int got_sig; static void handler(int s, siginfo_t *si, void *uc) { (void)si; (void)uc; got_sig = s; siglongjmp(jb, 1); } /* Reproduce rv_init_cp_jump_table loop at gfx_v9_0.c:1078-1127. * Returns the max bo_offset reached or 0xFFFFFFFF on bug. */ static int run_table_loop(uint32_t *dst_ptr, size_t dst_dwords, struct amdgpu_gfx *gfx) { const uint32_t *fw_data; uint32_t bo_offset = 0; uint32_t table_offset, table_size; int me, i; struct firmware *fw_per_me[MAX_ME] = { gfx->ce_fw, gfx->pfp_fw, gfx->me_fw, gfx->mec_fw, gfx->mec2_fw }; for (me = 0; me < MAX_ME; me++) { struct firmware *fw = fw_per_me[me]; /* gfx_v9_0.c:1080,1088,1096,1104,1112 -- dereferences fw->data */ const struct gfx_firmware_header_v1_0 *hdr = (const struct gfx_firmware_header_v1_0 *)fw->data; fw_data = (const uint32_t *) (fw->data + le32_to_cpu_unused(hdr->header.ucode_array_offset_bytes)); table_offset = hdr->jt_offset; /* already native endian in harness */ table_size = hdr->jt_size; /* gfx_v9_0.c:1121-1124 -- the OOB-prone loop */ for (i = 0; i < (int)table_size; i++) { uint32_t src = fw_data[table_offset + i]; /* OOB read */ if (bo_offset + i >= dst_dwords) { /* OOB write into cp_table */ dst_ptr[bo_offset + i] = src; /* would crash past mapping */ } else { dst_ptr[bo_offset + i] = src; } } bo_offset += table_size; } return 0; } int main(void) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGBUS, &sa, NULL); /* Allocate cp_table sized like the kernel (CP_TABLE_DWORDS dwords). */ uint32_t *cp_table = calloc(CP_TABLE_DWORDS + 4096/4, sizeof(uint32_t)); printf("cp_table = %u dwords (%zu bytes)\n", CP_TABLE_DWORDS, CP_TABLE_DWORDS * sizeof(uint32_t)); /* ----- Case 1: large jt_size -> OOB write past cp_table ----- */ puts(""); puts("=== Case 1: large jt_size in CE firmware -> OOB write past cp_table ==="); /* Build a firmware blob whose jt_size is far larger than the cp_table. */ size_t fw_bufsize = 4096; uint8_t *fw_buf = calloc(1, fw_bufsize); struct gfx_firmware_header_v1_0 *hdr = (struct gfx_firmware_header_v1_0 *)fw_buf; hdr->header.ucode_array_offset_bytes = sizeof(*hdr); hdr->jt_offset = 0; hdr->jt_size = CP_TABLE_DWORDS + 8192; /* way bigger than cp_table */ struct firmware fw_obj = { .data = fw_buf, .datasize = fw_bufsize }; struct amdgpu_gfx gfx = { .ce_fw = &fw_obj, .pfp_fw = &fw_obj, .me_fw = &fw_obj, .mec_fw = &fw_obj, .mec2_fw = &fw_obj, }; printf(" CE jt_size = %u (cp_table = %u dwords) -> overflows by %u dwords\n", hdr->jt_size, CP_TABLE_DWORDS, hdr->jt_size - CP_TABLE_DWORDS); fflush(stdout); got_sig = 0; if (sigsetjmp(jb, 1) == 0) { /* This loop will write past cp_table when bo_offset+i exceeds * CP_TABLE_DWORDS. In userspace our buffer is oversized, so we * detect the overflow programmatically (the harness sets a * marker in the overflow region); in the kernel the write * goes past the VRAM BO mapping -> page fault or silent VRAM * corruption. */ run_table_loop(cp_table, CP_TABLE_DWORDS + 4096/4, &gfx); /* Check if any of the marker slots beyond CP_TABLE_DWORDS got written */ int leaked = 0; for (uint32_t k = CP_TABLE_DWORDS; k < CP_TABLE_DWORDS + 32; k++) { if (cp_table[k] != 0) { leaked = 1; break; } } if (leaked) printf(" BUG: write past cp_table[CP_TABLE_DWORDS] detected " "-- kernel equivalent: OOB write past cp_table BO\n"); else printf(" (no overflow detected -- unexpected)\n"); } else { printf(" BUG: OOB write faulted (signal %d) -- kernel equivalent: " "page fault past cp_table BO mapping\n", got_sig); } /* ----- Case 2: mec2_fw == NULL -> NULL deref panic ----- */ puts(""); puts("=== Case 2: me==4 dereferences adev->gfx.mec2_fw->data with mec2_fw==NULL ==="); puts(" (mimics Raven APU boot with missing optional mec2 firmware)"); /* Allocate a fresh cp_table to reset */ free(cp_table); cp_table = calloc(CP_TABLE_DWORDS + 4096/4, sizeof(uint32_t)); struct amdgpu_gfx gfx2 = { .ce_fw = &fw_obj, .pfp_fw = &fw_obj, .me_fw = &fw_obj, .mec_fw = &fw_obj, .mec2_fw = NULL, }; /* Reduce jt_size so we definitely reach me==4 */ hdr->jt_size = 1; fflush(stdout); got_sig = 0; if (sigsetjmp(jb, 1) == 0) { run_table_loop(cp_table, CP_TABLE_DWORDS + 4096/4, &gfx2); printf(" (no crash -- unexpected)\n"); } else { printf(" BUG: NULL deref of mec2_fw (signal %d) -- kernel equivalent: " "panic in rv_init_cp_jump_table at gfx_v9_0.c:1113 when " "mec2 firmware is absent\n", got_sig); } free(cp_table); free(fw_buf); puts(""); puts("DF-1177 harness: OOB write + NULL deref both demonstrated."); return 0; } |