DF-1178 / 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 | /* * DF-1178 harness: demonstrates the indexing logic flaw in * gfx_v9_1_parse_ind_reg_list() โ a static function inside the amdgpu * driver's gfx_v9_0.c. The bug is reproduced by extracting the exact * parser logic into userspace and feeding it the kind of crafted * register_list_format the function would receive from RLC v2.1 firmware. * * The vulnerable function runs *only* when the amdgpu driver attaches to * real AMD Vega-class GPU hardware (gfx_v9_0_init_pg() at line 2396 is * invoked from gfx_v9_0_hw_init(), the .hw_init entry of the GFX v9 IP * block; it then calls gfx_v9_1_init_rlc_save_restore_list() at line 2405 * only when adev->gfx.rlc.is_rlc_v2_1 == true, which itself is set only * when the RLC firmware header carries v2.1 magic during gfx_v9_0_init_rlc() * at line 673). This guest has no AMD GPU (vgapci0 = vendor 0x1234 stdvga), * so the kernel-side path is unreachable here โ the harness exists to make * the flaw concrete and reproducible at the logic level. * * Confirmed bug: gfx_v9_0.c:2096-2098 * WARN_ON(*indirect_start_offsets_count >= max_start_offsets_count); // non-fatal * indirect_start_offsets[*indirect_start_offsets_count] = ...; // unconditional write * *indirect_start_offsets_count = *indirect_start_offsets_count + 1; * WARN_ON in DragonFly (sys/dev/drm/include/asm/bug.h:43-49) only kprintf's * a warning and returns the condition โ it does NOT stop execution. With * max_start_offsets_count == ARRAY_SIZE(indirect_start_offsets) == 10 (the * caller at gfx_v9_0.c:2126 allocates exactly 10 ints on the stack), a * crafted RLC v2.1 firmware with >10 indirect blocks overflows * indirect_start_offsets[] past the 10-int frame, smashing saved RBP / the * return address โ CWE-121 stack-based buffer overflow. * * Also at gfx_v9_0.c:2100-2117 the inner while loop does * while (register_list_format[indirect_offset] != 0xFFFFFFFF) { * indirect_offset += 2; * for (idx = 0; idx < unique_indirect_reg_count; idx++) { ... } * BUG_ON(idx >= unique_indirect_reg_count); * ... * indirect_offset++; * } * with NO check that indirect_offset < list_size, so a firmware with no * 0xFFFFFFFF terminator walks off the heap kmalloc() at gfx_v9_0.c:2133-2134 * โ CWE-125 OOB heap read. And BUG_ON() expands to panic() in DragonFly * (sys/dev/drm/include/asm/bug.h:33-37), so >8 unique indirect IDs in the * firmware is an unconditional kernel panic / local DoS. * * Compile (in guest as unprivileged maxx): * cc -O2 -o harness harness.c * Run: * ./harness * * Expected output: a printout showing the overflow happens (the WARN fires * and the write proceeds anyway), and the BUG_ON-equivalent panic marker. */ #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #define MAX_START_OFFSETS 10 #define UNIQUE_INDIRECT_REG_COUNT 8 #define LIST_SIZE 1024 /* Mirror of unique_indirect_regs[8] / indirect_start_offsets[10] on the * stack of gfx_v9_1_init_rlc_save_restore_list(). */ static int indirect_start_offsets[MAX_START_OFFSETS]; static int indirect_start_offsets_count; static int unique_indirect_regs[UNIQUE_INDIRECT_REG_COUNT]; static int warn_count; static int overflow_count; static int bug_panic; /* Faithful transcription of gfx_v9_1_parse_ind_reg_list() with WARN_ON * converted to a non-fatal printf+continue (matching DragonFly semantics) * and BUG_ON converted to a panic marker that aborts the harness. */ static void parse_ind_reg_list(const int *register_list_format, int indirect_offset, int list_size, int *unique_indirect_regs, int unique_indirect_reg_count, int *indirect_start_offsets, int *indirect_start_offsets_count, int max_start_offsets_count) { int idx; for (; indirect_offset < list_size; indirect_offset++) { /* WARN_ON โ non-fatal: prints + continues (sys/dev/drm/include/asm/bug.h:43). */ if (*indirect_start_offsets_count >= max_start_offsets_count) { warn_count++; /* NOTE: in the real kernel this proceeds anyway and writes OOB. * In the harness we deliberately DO the write into a too-small * (simulated) buffer so the overflow count is observable. */ } /* Unconditional write past the buffer when count >= max. */ if (*indirect_start_offsets_count >= max_start_offsets_count) overflow_count++; else indirect_start_offsets[*indirect_start_offsets_count] = indirect_offset; *indirect_start_offsets_count = *indirect_start_offsets_count + 1; while (register_list_format[indirect_offset] != 0xFFFFFFFF) { if (indirect_offset + 2 >= list_size) { /* Real kernel lacks this check -> reads past the heap buffer. */ printf("[oob-read] indirect_offset=%d walks past list_size=%d " "(CWE-125 at gfx_v9_0.c:2100-2101)\n", indirect_offset, list_size); return; } indirect_offset += 2; for (idx = 0; idx < unique_indirect_reg_count; idx++) { if (unique_indirect_regs[idx] == register_list_format[indirect_offset] || !unique_indirect_regs[idx]) break; } /* BUG_ON โ expands to panic() in DragonFly. */ if (idx >= unique_indirect_reg_count) { bug_panic++; printf("[panic] BUG_ON(idx >= unique_indirect_reg_count) " "at gfx_v9_0.c:2111 (>%d unique indirect IDs)\n", unique_indirect_reg_count); return; } if (!unique_indirect_regs[idx]) unique_indirect_regs[idx] = register_list_format[indirect_offset]; indirect_offset++; } } } int main(void) { /* Build a crafted register_list_format that has 14 indirect blocks * (each ending in 0xFFFFFFFF). The first 10 fit; blocks 11-14 * overflow indirect_start_offsets[] on the kernel stack. */ int *fmt = calloc(LIST_SIZE, sizeof(int)); if (!fmt) { perror("calloc"); return 1; } /* Each indirect block in the simplified grammar: * [<start-marker> 0xFFFFFFFF] * plus one inner slot (so indirect_offset+=2; lookup; indirect_offset++) * giving 4 ints per indirect block. */ int i, pos = 4; /* skip reg_list_format_direct_reg_list_length */ int block; for (block = 0; block < 14; block++) { fmt[pos++] = block + 100; /* start marker (anything != 0xFFFFFFFF) */ fmt[pos++] = block + 200; /* inner register id (force unique -> BUG_ON after 8) */ fmt[pos++] = 0; /* dummy slot */ fmt[pos++] = 0xFFFFFFFF; /* terminator */ if (pos + 8 >= LIST_SIZE) break; } int list_size = pos + 4; fmt[list_size - 1] = 0xFFFFFFFF; parse_ind_reg_list(fmt, 4 /* indirect_offset start */, list_size, unique_indirect_regs, UNIQUE_INDIRECT_REG_COUNT, indirect_start_offsets, &indirect_start_offsets_count, MAX_START_OFFSETS); printf("=== DF-1178 logic-level result ===\n"); printf("WARN_ON fires (non-fatal): %d time(s)\n", warn_count); printf("Stack-buffer overflows past indirect_start_offsets[10]: %d slot(s) " "(CWE-121 at gfx_v9_0.c:2097)\n", overflow_count); printf("BUG_ON panics (CWE-assertion): %d\n", bug_panic); printf("\n"); printf("On this guest the kernel path is HW-gated: gfx_v9_0_init_pg()\n"); printf("(gfx_v9_0.c:2396) runs only during amdgpu attach to a real AMD\n"); printf("Vega-class GPU (no AMD PCI device on this guest; vgapci0 is\n"); printf("QEMU stdvga vendor 0x1234). The bug is real but unreachable here.\n"); free(fmt); return 0; } |