DF-1296 / 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 | /* * DF-1296 harness: unchecked VBIOS UVD/VCE/ACP level counts overflow the * fixed-size SMC arrays in ci_populate_smc_uvd/vce/acp_level (ci_smumgr.c). * * Object-level proof. The ci (CIK / Bonaire/Hawaii) powerplay code runs only * on AMD Sea Islands GPUs; the audit QEMU guest has a QEMU std-vga * (chip 0x11111234), NOT an AMD GPU, so the path is not runtime-reachable * here. This harness replays the exact loop with a VBIOS-derived count that * exceeds the fixed array size. * * Cited path: sys/dev/drm/amd/powerplay/smumgr/ci_smumgr.c:1525 (UvdLevelCount * = (u8)uvd_table->count), :1527 loop count<UvdLevelCount writes * UvdLevel[count] (:1528+). VCE twin :1566/:1569, ACP twin :1598/:1601. * Arrays: UvdLevel[SMU7_MAX_LEVELS_UVD=8] (smu7_discrete.h:328, smu7.h:45), * VceLevel[8] (:329), AcpLevel[8] (:330). count source: VBIOS * ATOM_PPLIB numEntries (u8, no cap) parsed in processpptables.c:1097/1130/1189. * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #define SMU7_MAX_LEVELS_UVD 8 /* smu7.h:45 */ #define SMU7_MAX_LEVELS_VCE 8 /* smu7.h:46 */ #define SMU7_MAX_LEVELS_ACP 8 /* smu7.h:47 */ /* Mirror of smu7_discrete.h:240 SMU7_Discrete_UvdLevel (16 bytes) */ struct SMU7_Discrete_UvdLevel { uint32_t VclkFrequency; uint32_t DclkFrequency; uint16_t MinVddc; uint8_t MinVddcPhases; uint8_t VclkDivider; uint8_t DclkDivider; uint8_t padding[3]; }; /* Mirror of smu7_discrete.h:253 SMU7_Discrete_ExtClkLevel (8 bytes) */ struct SMU7_Discrete_ExtClkLevel { uint32_t Frequency; uint16_t MinVoltage; uint8_t MinPhases; uint8_t Divider; }; /* Minimal slice of SMU7_Discrete_DpmTable around UvdLevelCount/UvdLevel. The * real struct (smu7_discrete.h:300+) has VceLevel[8], AcpLevel[8], SamuLevel[8], * Ulv, SclkStepSize, Smio[], boot levels... immediately after UvdLevel[8] -- all * of which get clobbered by the overflow. The guard below stands in for that. */ struct DpmTable_slice { uint8_t UvdLevelCount; uint8_t VceLevelCount; uint8_t AcpLevelCount; uint8_t SamuLevelCount; uint8_t pad[4]; struct SMU7_Discrete_UvdLevel UvdLevel[SMU7_MAX_LEVELS_UVD]; struct SMU7_Discrete_ExtClkLevel VceLevel[SMU7_MAX_LEVELS_VCE]; /* adjacent victim */ struct SMU7_Discrete_ExtClkLevel guard[64]; }; int main(void) { struct DpmTable_slice *table = calloc(1, sizeof(*table)); if (!table) { perror("calloc"); return 1; } memset(table->guard, 0xAA, sizeof(table->guard)); printf("[DF-1296] UvdLevel[%d] (entry=%zu bytes), VceLevel[%d] adjacent\n", SMU7_MAX_LEVELS_UVD, sizeof(struct SMU7_Discrete_UvdLevel), SMU7_MAX_LEVELS_VCE); /* attacker: a VBIOS power table whose UVD numEntries (u8) exceeds 8. * processpptables.c:1097 copies numEntries into uvd_table->count verbatim * (no cap against SMU7_MAX_LEVELS_UVD). */ uint8_t vbios_count = 40; /* >> SMU7_MAX_LEVELS_UVD(8) */ printf("[DF-1296] attacker VBIOS UVD numEntries=%u (max array=%d)\n", vbios_count, SMU7_MAX_LEVELS_UVD); /* === ci_smumgr.c:1525-1552 (the unbounded loop) === */ table->UvdLevelCount = (uint8_t)vbios_count; /* :1525 */ uint8_t count; for (count = 0; count < table->UvdLevelCount; count++) { /* :1527 */ /* attacker-shaped fields from uvd_table->entries[count] :1528-1551 */ table->UvdLevel[count].VclkFrequency = 0x100000 + count; table->UvdLevel[count].DclkFrequency = 0x200000 + count; table->UvdLevel[count].MinVddc = 0x1000; table->UvdLevel[count].VclkDivider = (uint8_t)count; } if (vbios_count > SMU7_MAX_LEVELS_UVD) { printf("[DF-1296] BUG CONFIRMED: loop wrote UvdLevel[0..%u] but only " "UvdLevel[0..7] exists -> %u entries (%zu bytes) overflow past " "UvdLevel into VceLevel/AcpLevel/SamuLevel/Ulv/Smio/...\n", vbios_count-1, vbios_count - SMU7_MAX_LEVELS_UVD, (size_t)(vbios_count - SMU7_MAX_LEVELS_UVD) * sizeof(struct SMU7_Discrete_UvdLevel)); } /* count clobbered victims (adjacent slab / sibling fields) */ unsigned hit = 0; for (unsigned g = 0; g < 64; g++) { uint8_t *p = (uint8_t *)&table->VceLevel[g], *e = p + sizeof(table->VceLevel[g]); for (; p < e; p++) if (*p != 0x00 && *p != 0xAA) { hit++; break; } /* also flag VceLevel itself (in-bounds adjacent field, not the guard) */ } /* specifically: did we corrupt VceLevel[0] (the immediate adjacent field)? */ int vce_hit = memcmp(&table->VceLevel[0], (uint8_t[]){0,0,0,0,0,0,0,0}, 8) != 0; printf("[DF-1296] adjacent VceLevel[0] corrupted: %s ; guard region hit: " "%u entries\n", vce_hit ? "YES (overflow into sibling field)" : "no", hit); puts("\n--- Fix demonstration ---"); /* clamp count to SMU7_MAX_LEVELS_UVD (and likewise VCE/ACP): */ uint8_t clamped = vbios_count > SMU7_MAX_LEVELS_UVD ? SMU7_MAX_LEVELS_UVD : vbios_count; printf("[DF-1296] FIX: clamp UvdLevelCount %u -> %u -> loop stays in " "UvdLevel[0..7], no overflow\n", vbios_count, clamped); free(table); return (vbios_count > SMU7_MAX_LEVELS_UVD) ? 0 : 2; } |