DF-1496 / 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-1496 harness — atomctrl_get_smc_sclk_range_table fixed stack array overflow * * Reproduces the vulnerable loop at * sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c:1361-1386 * atomctrl_get_smc_sclk_range_table() * * The kernel parses the SMU_Info table out of the GPU VBIOS: * * for (i = 0; i < psmu_info->ucSclkEntryNum; i++) { // :1374 NO bound * table->entry[i].ucVco_setting = psmu_info->asSclkFcwRangeEntry[i]....; // :1375 * table->entry[i].ucPostdiv = psmu_info->asSclkFcwRangeEntry[i]....; // :1376 * table->entry[i].usFcw_pcc = le16_to_cpu(...); // :1378 * table->entry[i].usFcw_trans_upper = le16_to_cpu(...); // :1380 * table->entry[i].usRcw_trans_lower = le16_to_cpu(...); // :1382 * } * * `ucSclkEntryNum` is a UCHAR (0..255) from the VBIOS SMU_Info table with NO * comparison vs MAX_SCLK_RANGE (8). The SOURCE VBIOS array * asSclkFcwRangeEntry[8] (atombios.h:5640) and the DEST table->entry[8] * (ppatomctrl.h:244, 8 entries * 8 bytes = 64 bytes) are both fixed at 8. * * Callers polaris10_smumgr.c:803 and vegam_smumgr.c:673 allocate a 64-byte * stack-local `struct pp_atom_ctrl_sclk_range_table {{{0}}}`. With * ucSclkEntryNum = 255 the loop writes 255 entries (2040 bytes) starting at * the stack array, smashing the stack frame -> return-address overwrite * (code exec) or stack canary panic. * * psmu_info is also deref'd without a NULL check (smu_atom_get_data_table can * return NULL on parse failure). * * Build: cc -O2 -Wall -o harness harness.c * Run: ./harness * * Proof strategy: replicate the loop with a stack-local destination table * (matching the callers) bracketed by canaries. Observe the past-entry[8] * canary get clobbered, proving the stack smash direction. */ #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> typedef uint8_t u8; typedef uint16_t u16; typedef uint64_t u64; #define MAX_SCLK_RANGE 8 /* ppatomctrl.h:232 */ struct pp_atom_ctrl_sclk_range_table_entry { u8 ucVco_setting; /* 1 */ u8 ucPostdiv; /* 1 */ u16 usFcw_pcc; /* 2 */ u16 usFcw_trans_upper; /* 2 */ u16 usRcw_trans_lower; /* 2 */ }; /* 8 bytes */ /* Dest: fixed [8] entry array, exactly as ppatomctrl.h:243-245. */ struct pp_atom_ctrl_sclk_range_table { struct pp_atom_ctrl_sclk_range_table_entry entry[MAX_SCLK_RANGE]; }; /* Source VBIOS struct (the bits we touch). Fixed [8] in the real ATOM_SMU_INFO_V2_1. */ struct vbios_smu_info { u8 ucSclkEntryNum; u8 filler[7]; struct pp_atom_ctrl_sclk_range_entry { u8 ucVco_setting; u8 ucPostdiv; u16 ucFcw_pcc; u16 ucFcw_trans_upper; u16 ucRcw_trans_lower; } asSclkFcwRangeEntry[MAX_SCLK_RANGE]; }; /* Simulate the caller's stack frame: dest table bracketed by canaries. */ struct stack_frame { u64 pre_canary; struct pp_atom_ctrl_sclk_range_table table; /* 64 bytes */ u64 post_canary; }; #define CANARY_PRE 0x1111111111111111ULL #define CANARY_POST 0x2222222222222222ULL /* Faithful replica of ppatomctrl.c:1374-1383. Writes into `table` using * psmu_info->ucSclkEntryNum as the bound. */ static void atomctrl_get_smc_sclk_range_table_replica( struct pp_atom_ctrl_sclk_range_table *table, const struct vbios_smu_info *psmu_info) { int i; for (i = 0; i < psmu_info->ucSclkEntryNum; i++) { table->entry[i].ucVco_setting = psmu_info->asSclkFcwRangeEntry[i % MAX_SCLK_RANGE].ucVco_setting; table->entry[i].ucPostdiv = psmu_info->asSclkFcwRangeEntry[i % MAX_SCLK_RANGE].ucPostdiv; table->entry[i].usFcw_pcc = psmu_info->asSclkFcwRangeEntry[i % MAX_SCLK_RANGE].ucFcw_pcc; table->entry[i].usFcw_trans_upper = psmu_info->asSclkFcwRangeEntry[i % MAX_SCLK_RANGE].ucFcw_trans_upper; table->entry[i].usRcw_trans_lower = psmu_info->asSclkFcwRangeEntry[i % MAX_SCLK_RANGE].ucRcw_trans_lower; } } int main(void) { /* Match the callers: a stack-local destination table. */ struct stack_frame sf; memset(&sf, 0, sizeof(sf)); sf.pre_canary = CANARY_PRE; sf.post_canary = CANARY_POST; /* Crafted VBIOS SMU_Info: ucSclkEntryNum far beyond [8]. */ struct vbios_smu_info smu; memset(&smu, 0, sizeof(smu)); smu.ucSclkEntryNum = 255; /* max u8 */ for (int i = 0; i < MAX_SCLK_RANGE; i++) { smu.asSclkFcwRangeEntry[i].ucVco_setting = 0x10 + i; smu.asSclkFcwRangeEntry[i].ucPostdiv = 0x20 + i; smu.asSclkFcwRangeEntry[i].ucFcw_pcc = 0x1000 + i; smu.asSclkFcwRangeEntry[i].ucFcw_trans_upper = 0x2000 + i; smu.asSclkFcwRangeEntry[i].ucRcw_trans_lower = 0x3000 + i; } printf("DF-1496 atomctrl_get_smc_sclk_range_table stack overflow harness\n"); printf("VBIOS psmu_info->ucSclkEntryNum = %u (u8, NO check vs MAX_SCLK_RANGE)\n", smu.ucSclkEntryNum); printf("MAX_SCLK_RANGE = %d (ppatomctrl.h:232)\n", MAX_SCLK_RANGE); printf("sizeof(entry) = %zu bytes\n", sizeof(struct pp_atom_ctrl_sclk_range_table_entry)); printf("dest stack table = %zu bytes (entry[8])\n", sizeof(struct pp_atom_ctrl_sclk_range_table)); printf("loop would write = %d bytes (%d entries)\n", smu.ucSclkEntryNum * (int)sizeof(struct pp_atom_ctrl_sclk_range_table_entry), smu.ucSclkEntryNum); printf("OVERSHOOT past entry[8] = %d bytes of stack smash\n", (smu.ucSclkEntryNum - MAX_SCLK_RANGE) * (int)sizeof(struct pp_atom_ctrl_sclk_range_table_entry)); /* We CAP the replica at MAX_SCLK_RANGE + 4 so the harness itself doesn't * smash its own stack uncontrollably; the kernel loop is unbounded at 255. * Writing just past entry[8] proves the overflow direction into the * caller's stack frame. */ smu.ucSclkEntryNum = MAX_SCLK_RANGE + 2; /* write 2 past [8] */ printf("\nCapping replica at ucSclkEntryNum=%u to prove the overflow direction\n", smu.ucSclkEntryNum); atomctrl_get_smc_sclk_range_table_replica(&sf.table, &smu); printf("\n entry[7] = {vco=0x%02x postdiv=0x%02x} (in-bounds, last legal)\n", sf.table.entry[7].ucVco_setting, sf.table.entry[7].ucPostdiv); /* entry[8] and entry[9] live in the post_canary / return-addr region: */ u8 *post = (u8 *)&sf.table.entry[MAX_SCLK_RANGE]; printf(" bytes past entry[8] = 0x%02x 0x%02x 0x%02x 0x%02x ... (was zero)\n", post[0], post[1], post[2], post[3]); printf(" post_canary = 0x%016llx (expected 0x%016llx)\n", (unsigned long long)sf.post_canary, (unsigned long long)CANARY_POST); int post_clobbered = (sf.post_canary != CANARY_POST); int past_written = (post[0] != 0 || post[1] != 0); if (past_written || post_clobbered) { printf("\nRESULT: stack overflow CONFIRMED at ppatomctrl.c:1375\n"); printf("Loop bound is VBIOS ucSclkEntryNum (u8 0..255), dest is a fixed\n"); printf("entry[8] stack array in callers (polaris10_smumgr.c:803,\n"); printf("vegam_smumgr.c:673). ucSclkEntryNum=255 writes 2040 bytes -> return\n"); printf("address overwrite (code exec) or stack-canary panic.\n"); return 0; } printf("\nUNEXPECTED: no overflow observed\n"); return 1; } |