DF-1134 / harness.c
/* * DF-1134 harness: cz_init_cp_jump_table jt_offset/jt_size OOB (source-trace) * * Replicates cz_init_cp_jump_table() (gfx_v8_0.c:1301-1364) for Carrizo/Stoney: * * gfx_v8_0.c:1321 table_offset = le32(hdr->jt_offset); // u32, UNVALIDATED * gfx_v8_0.c:1322 table_size = le32(hdr->jt_size); // u32, UNVALIDATED * gfx_v8_0.c:1318 fw_data = fw->data + ucode_array_offset_bytes; // also unchecked * gfx_v8_0.c:1357 for(i=0;i<table_size;i++) * gfx_v8_0.c:1358 dst_ptr[bo_offset+i] = le32(fw_data[table_offset+i]); * gfx_v8_0.c:1362 bo_offset += table_size; // accumulates across 4..5 ME fw blobs * * dst_ptr = cp_table_ptr: a VRAM BO of cp_table_size = * ALIGN(96*5*4, 2048) + 64*1024 = 2048 + 65536 = 67584 bytes (gfx_v8_0.c:1407). * cp_table_size/4 = 16896 dwords. * * fw_data points into the request_firmware() kmalloc blob (fw->datasize bytes). * * No bounds checks anywhere; amdgpu_ucode_validate() only checks * fw->datasize == hdr->size_bytes. * * -> OOB WRITE: bo_offset+i >= 16896 writes past the cp_table VRAM BO * (corrupts adjacent VRAM / faults). * -> OOB READ: table_offset+i >= datasize/4 reads past the firmware blob * into adjacent kernel heap (info leak source). * * Requires an AMD Carrizo/Stoney APU (amdgpu) loading crafted CE/PFP/ME/MEC * firmware headers. Not present on the audit QEMU guest. */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #define ALIGN(x,a) (((x)+(a)-1) & ~((a)-1)) #define CP_TABLE_SIZE (ALIGN(96*5*4, 2048) + (64*1024)) /* gfx_v8_0.c:1407 */ int main(void) { uint32_t cp_dwords = CP_TABLE_SIZE / 4; printf("cp_table_size = %d bytes (%d dwords capacity for dst_ptr[])\n\n", CP_TABLE_SIZE, cp_dwords); /* Model the 4 (or 5) ME firmware blobs, each contributing a jt_offset/jt_size * that a malicious firmware header could set. */ 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 }, }; uint32_t bo_offset = 0; int oob_write = 0, oob_read = 0; for (int m = 0; m < 4; m++) { /* emulate the per-ME loop bounds */ for (uint32_t i = 0; i < me[m].jt_size; i++) { uint32_t dst_idx = bo_offset + i; uint32_t src_idx = me[m].jt_offset + i; if (dst_idx >= cp_dwords) oob_write++; if (src_idx >= me[m].fw_dwords) oob_read++; } 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 MEs = %u (capacity %u dwords)\n", bo_offset, cp_dwords); printf("modeled OOB WRITE iterations (dst past cp_table) = %d\n", oob_write); printf("modeled OOB READ iterations (src past fw blob) = %d\n", oob_read); if (oob_write > 0 || oob_read > 0 || bo_offset > cp_dwords) { printf("\nDF-1134: CONFIRMED OOB write past cp_table VRAM BO AND/OR OOB read past firmware blob\n"); } else { printf("DF-1134: NOT reproduced\n"); } /* ---- WITH FIX: validate jt_offset+jt_size<=fw_dwords and bo_offset+jt_size<=cp_dwords ---- */ printf("\n--- WITH FIX (validate jt bounds vs fw blob and cp_table; skip if invalid) ---\n"); uint32_t fb = 0; int fw_ob=0, fr_ob=0; for (int m = 0; m < 4; 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=%d ; OOB read iters remaining=%d (expect 0)\n", fw_ob, fr_ob); printf("DF-1134 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; } |