DF-1163 / 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 | /* * DF-1163 harness: gfx_v7_0_init_cp_pg_table jt_offset/jt_size OOB * * Replicates gfx_v7_0_init_cp_pg_table() (sys/dev/drm/amd/amdgpu/gfx_v7_0.c * :3787-3853) for Sea Islands (CIK). This is the gfx_v7 twin of DF-1134 * (gfx_v8_0 cz_init_cp_jump_table). * * gfx_v7_0.c:3300 cp_table_size = ALIGN(CP_ME_TABLE_SIZE * 5 * 4, 2048) * gfx_v7_0.c:3301 += 64 * 1024 -> 67584 bytes * cikd.h:36 #define CP_ME_TABLE_SIZE 96 -> 16896 dwords * * gfx_v7_0.c:3800 dst_ptr = adev->gfx.rlc.cp_table_ptr; (the cp_table BO map) * for (me = 0; me < max_me; me++) { // max_me=4 (or 5 on KAVERI) * hdr = <ce|pfp|me|mec|mec2>_fw->data; * fw_data = fw->data + ucode_array_offset_bytes; // UNCHECKED * table_offset = le32(hdr->jt_offset); // UNVALIDATED u32 * table_size = le32(hdr->jt_size); // UNVALIDATED u32 * gfx_v7_0.c:3847 for (i = 0; i < table_size; i++) * gfx_v7_0.c:3848 dst_ptr[bo_offset + i] = * gfx_v7_0.c:3849 le32(fw_data[table_offset + i]); * gfx_v7_0.c:3852 bo_offset += table_size; // accumulates across all MEs * } * * No bounds checks anywhere: * - OOB WRITE (CWE-787): bo_offset+i >= 16896 writes past the cp_table VRAM BO. * - OOB READ (CWE-125): table_offset+i >= fw->datasize/4 reads past the * firmware kmalloc blob into adjacent kernel heap (info-leak source). * * amdgpu_ucode_validate() (amdgpu_ucode.c:256) only checks * fw->datasize == hdr->size_bytes and validates neither jt_offset, jt_size, * nor the ucode array offset. * * NOT triggerable on the audit guest: amdgpu is not in X86_64_GENERIC and the * guest has no AMD GPU. Harness proves the primitive by COUNTING OOB * write/read iterations (never performs the OOB access). */ #include <stdio.h> #include <stdint.h> #define ALIGN(x,a) (((x)+(a)-1) & ~((a)-1)) #define CP_ME_TABLE_SIZE 96 /* cikd.h:36 */ #define CP_TABLE_SIZE (ALIGN(CP_ME_TABLE_SIZE*5*4, 2048) + (64*1024)) /* gfx_v7_0.c:3300-3301 */ int main(void) { uint32_t cp_dwords = CP_TABLE_SIZE / 4; printf("cp_table_size = %d bytes (%d dwords capacity for dst_ptr[])\n", CP_TABLE_SIZE, cp_dwords); /* Model 4 (CIK) ME firmware blobs with attacker-chosen jt_offset/jt_size * and a finite firmware blob size (datasize/4 dwords). */ struct { const char *name; uint32_t jt_offset; uint32_t jt_size; uint32_t fw_dwords; } me[] = { { "CE", 0x10, 4000, 1024 }, /* jt_size 4000 alone is fine, but... */ { "PFP", 0x10000, 200, 1024 }, /* jt_offset way past fw blob -> OOB read */ { "ME", 0x20, 16896, 2048 }, /* jt_size exhausts whole dst capacity */ { "MEC", 0x30, 100, 512 }, }; const int max_me = 4; uint32_t bo_offset = 0; uint32_t oob_write = 0, oob_read = 0; for (int m = 0; m < max_me; m++) { for (uint32_t i = 0; i < me[m].jt_size; i++) { if (bo_offset + i >= cp_dwords) oob_write++; /* dst past cp_table BO */ if (me[m].jt_offset + i >= me[m].fw_dwords) oob_read++; /* src past fw blob */ } bo_offset += me[m].jt_size; printf("ME[%s] jt_offset=0x%x jt_size=%-5u fw_dwords=%-5u -> bo_offset now %u\n", me[m].name, me[m].jt_offset, me[m].jt_size, me[m].fw_dwords, bo_offset); } printf("\ncumulative bo_offset after all %d MEs = %u (capacity %u dwords)\n", max_me, bo_offset, cp_dwords); printf("modeled OOB WRITE iters (dst past cp_table) = %u\n", oob_write); printf("modeled OOB READ iters (src past fw blob) = %u\n", oob_read); if (oob_write > 0 || oob_read > 0 || bo_offset > cp_dwords) printf("\nDF-1163: CONFIRMED OOB write past cp_table VRAM BO AND/OR OOB read past firmware blob\n"); else printf("\nDF-1163: NOT reproduced\n"); /* ---- WITH FIX: reject jt_size/jt_offset that read past fw blob or write past cp_table ---- */ printf("\n--- WITH FIX (validate jt bounds vs fw blob and cp_table; skip if invalid) ---\n"); uint32_t fb = 0; uint32_t fw_ob = 0, fr_ob = 0; for (int m = 0; m < max_me; m++) { uint32_t ts = me[m].jt_size, to = me[m].jt_offset, fwd = me[m].fw_dwords; int reject = (ts > fwd) || (to > fwd - ts) || (ts > cp_dwords) || (fb > cp_dwords - ts); printf("ME[%s] jt_off=0x%-6x jt_size=%-5u fw_dw=%-5u -> %s\n", me[m].name, to, ts, fwd, reject ? "REJECTED (skip, safe)" : "accepted"); if (!reject) { for (uint32_t i = 0; i < ts; i++) { if (fb + i >= cp_dwords) fw_ob++; if (to + i >= fwd) fr_ob++; } fb += ts; } } printf("FIX result: OOB write iters remaining=%u ; OOB read iters remaining=%u (expect 0)\n", fw_ob, fr_ob); printf("DF-1163 FIX: %s\n", (fw_ob == 0 && fr_ob == 0) ? "VALIDATED - bounds validation rejects all OOB jt_offset/jt_size" : "INCOMPLETE"); return (oob_write > 0 || oob_read > 0 || bo_offset > cp_dwords) ? 0 : 1; } |