DF-1167 / 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 | /* * DF-1167 โ userspace harness for smu7_setup_dpm_tables_v0 NULL-deref + OOB read. * * Reconstructs the relevant struct layouts from * sys/dev/drm/amd/powerplay/inc/hwmgr.h * and replays the loop at * sys/dev/drm/amd/powerplay/hwmgr/smu7_hwmgr.c:716-721 * to demonstrate the kernel-mode NULL deref and OOB read in userspace. * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <signal.h> #include <setjmp.h> #include <unistd.h> /* --- mirror hwmgr.h struct layouts ----------------------------------- */ struct phm_clock_voltage_dependency_record { uint32_t clk; uint32_t v; }; struct phm_clock_voltage_dependency_table { uint32_t count; struct phm_clock_voltage_dependency_record entries[1]; }; union phm_cac_leakage_record { struct { uint16_t Vddc; uint32_t Leakage; }; struct { uint16_t Vddc1; uint16_t Vddc2; uint16_t Vddc3; }; }; struct phm_cac_leakage_table { uint32_t count; union phm_cac_leakage_record entries[1]; }; /* --- crash-catcher so the harness can keep running after the SIGSEGV -- */ static sigjmp_buf jb; static volatile int last_sig = 0; static void handler(int s, siginfo_t *si, void *uc) { (void)uc; last_sig = s; siglongjmp(jb, s); } /* Reproduces smu7_hwmgr.c:716-721 with caller-supplied pointers/counts. * Returns the value the buggy kernel line would have read, or crashes. */ static void repro_loop(struct phm_clock_voltage_dependency_table *sclk, struct phm_clock_voltage_dependency_table *mclk, struct phm_cac_leakage_table *std_v, uint32_t *out_vddc, uint32_t *out_leakage, uint32_t i) { /* smu7_hwmgr.c:717 */ out_vddc[i] = mclk->entries[i].v; /* smu7_hwmgr.c:718 */ out_leakage[i] = std_v->entries[i].Leakage; } int main(void) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGBUS, &sa, NULL); /* Allocate a normal sclk table with 3 entries (matches the * mandatory PP_ASSERT_WITH_CODE checks at smu7_hwmgr.c:677,679). */ struct phm_clock_voltage_dependency_table *sclk = malloc(sizeof(*sclk) + 2 * sizeof(sclk->entries[0])); sclk->count = 3; for (uint32_t i = 0; i < 3; i++) { sclk->entries[i].clk = 1000 + i * 100; sclk->entries[i].v = 1100 + i * 10; } struct phm_clock_voltage_dependency_table *mclk = malloc(sizeof(*mclk) + 2 * sizeof(mclk->entries[0])); mclk->count = 3; for (uint32_t i = 0; i < 3; i++) { mclk->entries[i].clk = 1000 + i * 100; mclk->entries[i].v = 1100 + i * 10; } uint32_t out_vddc[16] = {0}; uint32_t out_leakage[16] = {0}; /* ------------------------------------------------------------------ * CASE 1 โ cac_leakage_table == NULL (processpptables.c:1470 path * when usCACLeakageTableOffset == 0). Kernel: NULL-deref. * ------------------------------------------------------------------ */ printf("[case 1] std_voltage_table=NULL, sclk_count=%u -> " "reproducing kernel loop (smu7_hwmgr.c:716-721)...\n", sclk->count); fflush(stdout); last_sig = 0; if (sigsetjmp(jb, 1) == 0) { repro_loop(sclk, mclk, /*std_v=*/NULL, out_vddc, out_leakage, 0); printf(" (no crash -- unexpected)\n"); } else { printf(" BUG: dereferenced std_voltage_table (NULL) at i=0\n"); printf(" harness: caught signal %d (SIGSEGV/SIGBUS) -- " "kernel equivalent: NULL-deref panic in " "smu7_setup_dpm_tables_v0\n", last_sig); } /* ------------------------------------------------------------------ * CASE 2 โ cac_leakage_table->count < sclk->count. Kernel: OOB read. * ------------------------------------------------------------------ */ struct phm_cac_leakage_table *short_std = malloc(sizeof(*short_std) + 0 * sizeof(short_std->entries[0])); short_std->count = 1; short_std->entries[0].Leakage = 0xCAFEBABE; /* Force the loop bound (sclk->count) past short_std->count. * In the kernel, the loop at smu7_hwmgr.c:716 runs i in * [0, allowed_vdd_sclk_table->count). We make sclk->count=8 but * std_voltage_table only has 1 entry. */ sclk->count = 8; printf("\n[case 2] std_voltage_table->count=%u, sclk_count=%u -> " "OOB read past std_voltage_table->entries[]\n", short_std->count, sclk->count); uint32_t leaked_any = 0; for (uint32_t i = 0; i < sclk->count; i++) { last_sig = 0; if (sigsetjmp(jb, 1) == 0) { repro_loop(sclk, mclk, short_std, out_vddc, out_leakage, i); if (i >= short_std->count) { printf(" i=%u: BUG OOB read std_voltage_table->" "entries[%u].Leakage = 0x%08x " "(past allocated count=%u)\n", i, i, out_leakage[i], short_std->count); leaked_any = 1; } } else { printf(" i=%u: OOB access faulted (signal %d) -- " "kernel equivalent: OOB read past kmalloc'd " "cac_leakage_table\n", i, last_sig); leaked_any = 1; /* In the kernel the slab allocation is larger than * the requested size, so the read would succeed for * several entries before hitting an unmapped page. * We stop the userspace harness at the first fault. */ break; } } if (!leaked_any) printf(" (no OOB -- unexpected)\n"); printf("\nDF-1167 harness: both bug classes demonstrated.\n"); free(sclk); free(mclk); free(short_std); return 0; } |