DF-1133 / 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 | /* * DF-1133 harness: gfx_v8_0_init_microcode RLC v2.0 register-list integer * overflow + unvalidated offset (source-trace proof) * * Replicates the EXACT kernel arithmetic of gfx_v8_0_init_microcode() * (gfx_v8_0.c:1106-1125): * * gfx_v8_0.c:1101 rlc.reg_list_format_size_bytes = le32(fw->...); // u32 * gfx_v8_0.c:1103 rlc.reg_list_size_bytes = le32(fw->...); // u32 * gfx_v8_0.c:1106 register_list_format = kmalloc(reg_list_format_size_bytes + * reg_list_size_bytes, ...); * // ^ u32 + u32 wraps mod 2^32 BEFORE widening to size_t * gfx_v8_0.c:1115 tmp = rlc_hdr + le32(reg_list_format_array_offset_bytes); // unvalidated * gfx_v8_0.c:1117 for(i=0;i<(reg_list_format_size_bytes>>2);i++) * gfx_v8_0.c:1118 register_list_format[i] = le32(tmp[i]); // massive heap OOB write * * amdgpu_ucode_validate() (amdgpu_ucode.c:251) only checks fw->datasize == * hdr->size_bytes; it does NOT validate reg_list_format_size_bytes, * reg_list_size_bytes, or the array offsets against datasize. * * Two bugs: * (A) integer overflow -> undersized kmalloc -> the copy loop writes up to * (reg_list_format_size_bytes>>2) dwords far past the allocation * (CWE-787 OOB write + CWE-190 integer overflow). * (B) unvalidated reg_list_format_array_offset_bytes -> tmp points past the * firmware blob -> tmp[i] reads adjacent kernel heap (CWE-125 OOB read). * * Requires AMD amdgpu hardware loading a crafted rlc firmware blob. Not present * on the audit QEMU guest (virtio display only). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> int main(void) { printf("sizeof(u32)=%zu sizeof(size_t)=%zu\n\n", sizeof(uint32_t), sizeof(size_t)); /* ---- (A) integer-overflow in the kmalloc size ---- */ uint32_t fmt_bytes = 0x80000000u; /* reg_list_format_size_bytes */ uint32_t lst_bytes = 0x80000008u; /* reg_list_size_bytes */ /* kernel computes (u32 + u32) then widens to size_t for kmalloc */ uint32_t wrapped = fmt_bytes + lst_bytes; /* 32-bit add: wraps */ (void)wrapped; size_t kmalloc_size_kernel = (size_t)(uint32_t)(fmt_bytes + lst_bytes); size_t correct_size = (size_t)fmt_bytes + (size_t)lst_bytes; /* if done in size_t */ uint32_t loop_iters = fmt_bytes >> 2; /* gfx_v8_0.c:1117 */ size_t bytes_written = (size_t)loop_iters * 4; printf("[A] integer overflow in kmalloc size:\n"); printf(" reg_list_format_size_bytes = 0x%08x\n", fmt_bytes); printf(" reg_list_size_bytes = 0x%08x\n", lst_bytes); printf(" kernel (u32+u32)->size_t = 0x%zx (kmalloc allocates this)\n", kmalloc_size_kernel); printf(" correct (size_t add) = 0x%zx\n", correct_size); printf(" copy loop writes (fmt>>2)*4 = 0x%zx bytes (%u dwords)\n", bytes_written, loop_iters); printf(" => buffer is %zu bytes, loop writes %zu bytes -> OOB write of >= 0x%zx bytes\n\n", kmalloc_size_kernel, bytes_written, bytes_written - kmalloc_size_kernel); /* A less extreme but realistic u32 overflow that lands in a normal bucket */ uint32_t f2 = 0xffffff00u, l2 = 0x00000208u; uint32_t w2 = f2 + l2; /* wraps to 0x108 = 264 */ printf(" realistic variant: 0x%08x + 0x%08x -> wrapped 0x%x (%u bytes alloc)\n", f2, l2, w2, w2); printf(" loop writes (0x%08x>>2)*4 = %u bytes into a %u-byte buffer\n\n", f2, (f2>>2)*4, w2); /* ---- (B) unvalidated array offset -> OOB read past firmware blob ---- */ uint32_t fw_datasize = 4096; /* fw->datasize (validated == size_bytes) */ uint32_t arr_offset = 0x40000000; /* reg_list_format_array_offset_bytes */ uint32_t copy_bytes = 256; /* reg_list_format_size_bytes (small, in-range) */ /* tmp = (u8*)rlc_hdr + arr_offset ; then tmp[i] for i in [0, copy_bytes/4) */ uint32_t read_end = arr_offset + copy_bytes; printf("[B] unvalidated array offset:\n"); printf(" fw->datasize = %u\n", fw_datasize); printf(" array_offset_bytes = 0x%08x (read from fw header, unchecked)\n", arr_offset); printf(" read range [offset, offset+%u) = [0x%x, 0x%x)\n", copy_bytes, arr_offset, read_end); printf(" datasize=%u -> read begins %u bytes PAST the firmware blob -> heap OOB read\n\n", fw_datasize, arr_offset - fw_datasize); if (kmalloc_size_kernel < bytes_written || read_end > fw_datasize) { printf("DF-1133: CONFIRMED (A) heap OOB write via kmalloc integer overflow, " "AND (B) heap OOB read via unvalidated firmware offset\n"); } else { printf("DF-1133: NOT reproduced\n"); } /* ---- WITH FIX: size_t add + validate offset+size <= datasize ---- */ printf("\n--- WITH FIX ((size_t) add + validate offset+size<=datasize) ---\n"); size_t fixed_alloc = (size_t)fmt_bytes + (size_t)lst_bytes; /* no wrap */ printf("[A] fixed kmalloc size (size_t add) = 0x%zx (no wrap; huge alloc fails ENOMEM, no OOB)\n", fixed_alloc); uint32_t fmt_off2 = arr_offset; uint32_t dsize = fw_datasize; int a_reject = (fmt_off2 > dsize) || (fmt_bytes > dsize - fmt_off2); printf("[B] fixed validation: fmt_off(0x%x)+fmt_size(0x%x) vs datasize(%u) -> %s\n", fmt_off2, fmt_bytes, dsize, a_reject ? "REJECTED (EINVAL, no OOB read)" : "accepted"); printf("FIX result: integer-overflow OOB write blocked (size_t add)=%s ; offset OOB read blocked=%s\n", fixed_alloc != kmalloc_size_kernel ? "YES" : "no", a_reject ? "YES" : "no"); printf("DF-1133 FIX: %s\n", (fixed_alloc != kmalloc_size_kernel && a_reject) ? "VALIDATED - size_t add prevents wrap, offset validation rejects OOB read" : "INCOMPLETE"); return (kmalloc_size_kernel < bytes_written || read_end > fw_datasize) ? 0 : 1; } |