DF-1128 / 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 | /* * DF-1128 harness: amdgpu/si_dpm.c zero performance_level_count * * Confirmed primitives (source-level + userspace math harness): * [A] si_convert_power_state_to_smc:5665 -- performance_levels[count-1] * reads struct si_ps.performance_levels[-1] when count==0 (4-byte OOB * read into the preceding slab object / slab metadata). Result is used * as `threshold` and as a watermark comparison operand. * [B] si_dpm_get_sclk:7876 / si_dpm_get_mclk:7888 -- same OOB read pattern * in sysfs/debugfs sclk/mclk getters. * * NOTE on the finding summary's "memset(state_size=~SIZE_MAX) overflow": * amdgpu/si_dpm.c:5738-5740 computes state_size = sizeof(SWSTATE) + * ((count-1) * sizeof(LEVEL)). With the real struct layout (SWSTATE = 4-byte * header + 1 LEVEL), sizeof(SWSTATE) - sizeof(LEVEL) == 4. So state_size * evaluates to 4 (NOT ~SIZE_MAX). The memset at line 5743 only writes 4 * bytes. The "memset heap overflow" half of the finding summary is therefore * INCORRECT. The OOB read at line 5665 and 7876/7888 is REAL and is the * actual primitive. * * Trigger: a crafted VBIOS whose ATOM_PPLIB power state has ucNumDPMLevels==0 * (or all clockInfoIndex values out of range) leaves si_ps.performance_level_count * at its kzalloc-initialized 0. Code path: si_parse_power_table:7267-7280 * (the inner loop body never runs, so si_parse_pplib_clock_info is never * called and never sets performance_level_count). * * amdgpu.ko is not loaded on the QEMU audit guest (no AMD GPU), so the live * trigger requires SI hardware + a crafted VBIOS. This harness proves the * math/primitive is exploitable. * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdint.h> #include <string.h> #include <stdlib.h> #define SISLANDS_MAX_HARDWARE_POWERLEVELS 6 /* Real layout of struct rv7xx_pl (radeon.h) */ struct rv7xx_pl { uint32_t sclk; uint32_t mclk; uint16_t vddc; uint16_t vddci; uint16_t flags; uint16_t padding; }; /* Real layout of struct si_ps (si_dpm.h:614-617). Note performance_levels[] * is preceded ONLY by performance_level_count (u16). */ struct si_ps { uint16_t performance_level_count; uint8_t pad[6]; /* alignment to 8 */ struct rv7xx_pl performance_levels[SISLANDS_MAX_HARDWARE_POWERLEVELS]; }; /* Mirror of si_convert_power_state_to_smc amdgpu/si_dpm.c:5662-5665 */ static int buggy_threshold(struct si_ps *state, uint32_t *thresh_out) { if (state->performance_level_count > SISLANDS_MAX_HARDWARE_POWERLEVELS) return -1; /* only checks > MAX, not ==0 */ *thresh_out = state->performance_levels[state->performance_level_count - 1].sclk; return 0; } int main(void) { /* Place si_ps at offset 16 inside a larger buffer so we can see what's * at performance_levels[-1] without faulting. kzalloc(sizeof(si_ps)) * returns a slab object; performance_levels[-1] reads sizeof(rv7xx_pl) * bytes BEFORE the start of that object. */ uint8_t backing[256]; memset(backing, 0xCC, sizeof(backing)); struct si_ps *state = (struct si_ps *)&backing[16]; state->performance_level_count = 0; /* Leave performance_levels[0] initialized to 0 to isolate the -1 read */ printf("=== DF-1128 harness: crafted si_ps with performance_level_count==0 ===\n\n"); printf(" sizeof(struct si_ps) = %zu\n", sizeof(struct si_ps)); printf(" sizeof(struct rv7xx_pl) = %zu (one performance level)\n", sizeof(struct rv7xx_pl)); printf(" performance_levels[0] offset = %zu (inside si_ps)\n", __builtin_offsetof(struct si_ps, performance_levels)); printf(" performance_levels[-1] reads %zu bytes BEFORE performance_levels[0]\n", sizeof(struct rv7xx_pl)); printf(" -> reads offset %zd inside struct si_ps (i.e. BEFORE the object)\n\n", -(ssize_t)sizeof(struct rv7xx_pl)); uint32_t thresh; if (buggy_threshold(state, &thresh) == 0) { printf("[A] si_convert_power_state_to_smc:5665 OOB READ:\n"); printf(" threshold = performance_levels[count-1].sclk = 0x%08x\n", thresh); printf(" >>> In kernel: %zu-byte OOB read BEFORE allocated si_ps slab object <<<\n\n", sizeof(uint32_t)); } printf("[B] si_dpm_get_sclk:7876 / si_dpm_get_mclk:7888: same OOB pattern when\n"); printf(" `low` argument is false -- returns performance_levels[count-1].sclk/mclk.\n"); printf(" These are sysfs/debugfs readable via radeon_pm_info -> info leak of\n"); printf(" adjacent slab data (or panic if the read crosses a page boundary).\n\n"); printf("[C] Correctness check on the finding's `state_size underflow` claim:\n"); /* state_size = sizeof(SWSTATE) + ((count-1) * sizeof(LEVEL)) * With real layout: SWSTATE = 4 header + 1 LEVEL, so * sizeof(SWSTATE) - sizeof(LEVEL) = 4. Result: state_size == 4. */ printf(" sizeof(SWSTATE) - sizeof(LEVEL) == 4 (NOT ~SIZE_MAX)\n"); printf(" -> memset(smc_state, 0, 4) is safe; the `memset heap overflow`\n"); printf(" claim in the finding summary is INCORRECT. The real primitive\n"); printf(" is the OOB read at line 5665 (and 7876/7888), which this harness confirms.\n"); return 0; } |