DF-1136 / 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 | /* * DF-1136 harness: si_get_svi2_voltage_table count overflow (source-trace) * * Replicates si_get_svi2_voltage_table() (si_dpm.c:3939-3958): * * si_dpm.c:3951 voltage_table->count = voltage_dependency_table->count; * si_dpm.c:3952 for (i=0;i<voltage_table->count;i++) * si_dpm.c:3953 voltage_table->entries[i].value = dep->entries[i].v; * si_dpm.c:3954 voltage_table->entries[i].smio_low = 0; * * struct atom_voltage_table (radeon_mode.h:679-685): * u32 count; u32 mask_low; u32 phase_delay; * struct atom_voltage_table_entry entries[MAX_VOLTAGE_ENTRIES]; // MAX_VOLTAGE_ENTRIES = 32 * struct atom_voltage_table_entry { u16 value; u32 smio_low; }; // 6 bytes (packed) / 8 (align) * * `count` is sourced from VBIOS power tables (atom firmware ucNumEntries, u8 * 0..255) and stored into the u32 `voltage_dependency_table->count`. If a * malicious/corrupt VBIOS advertises count > 32 (up to 255 via u8, more via u32 * field), the loop writes entries[32..count-1] PAST the fixed array. * * Crucially, the GPIO voltage path at si_dpm.c:3973-3976 DOES trim via * si_trim_voltage_table_to_fit_state_table(SISLANDS_MAX_NO_VREG_STEPS), but the * two SVI2 call sites (si_dpm.c:3977-3982 vddc, si_dpm.c:3998-4004 vddci) call * si_get_svi2_voltage_table() with NO trimming. voltage_table is embedded in * struct evergreen_power_info, so entries[32+] overwrite adjacent fields * (cac_weights, powertune pointers) -> corruption/RIP control during si_dpm_enable. * * Requires an AMD Southern Islands (radeon) GPU with a malicious VBIOS (reflash * / KVM passthrough / QEMU-emulated). Not present on the audit QEMU guest. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define MAX_VOLTAGE_ENTRIES 32 struct atom_voltage_table_entry { uint16_t value; uint32_t smio_low; }; struct atom_voltage_table { uint32_t count; uint32_t mask_low; uint32_t phase_delay; struct atom_voltage_table_entry entries[MAX_VOLTAGE_ENTRIES]; /* in the kernel, the fields that follow `entries[32]` inside struct * evergreen_power_info sit right here. Model them as a canary. */ uint64_t following_canary; }; int main(void) { printf("MAX_VOLTAGE_ENTRIES = %d\n", MAX_VOLTAGE_ENTRIES); printf("sizeof(atom_voltage_table entries[]) = %zu\n", sizeof(struct atom_voltage_table_entry) * MAX_VOLTAGE_ENTRIES); printf("offset of entries[32] (first OOB) = %zu\n", __builtin_offsetof(struct atom_voltage_table, entries[MAX_VOLTAGE_ENTRIES])); printf("offset of following canary = %zu\n\n", __builtin_offsetof(struct atom_voltage_table, following_canary)); int any_oob = 0; /* VBIOS-sourced count values (ucNumEntries is u8: 0..255; the dependency * table field is u32 so an attacker/corrupt VBIOS can push higher). */ uint32_t counts[] = { 8, 32, 33, 48, 64, 255 }; for (size_t c = 0; c < sizeof(counts)/sizeof(counts[0]); c++) { uint32_t count = counts[c]; struct atom_voltage_table vt; memset(&vt, 0, sizeof(vt)); vt.following_canary = 0xDEADBEEFCAFEULL; /* exact kernel loop (si_dpm.c:3951-3955) — but STOP at the array bound * so we don't corrupt our own process; instead count the OOB writes. */ vt.count = count; uint32_t oob_writes = 0; for (uint32_t i = 0; i < count; i++) { if (i >= MAX_VOLTAGE_ENTRIES) { oob_writes++; continue; /* would be entries[i] = ... past the array */ } vt.entries[i].value = (uint16_t)(0x1000 + i); vt.entries[i].smio_low = 0; } printf("VBIOS count=%-4u entries written in-bounds=%u OOB writes past entries[32]=%u %s\n", count, count - oob_writes, oob_writes, oob_writes ? ">>> HEAP OOB WRITE into evergreen_power_info" : ""); if (oob_writes) any_oob++; } /* Concrete demonstration with count=48: place the table in a contiguous * struct and (with bounds removed) show entries[32..47] stomp the canary. */ { uint32_t count = 48; struct atom_voltage_table *vt = calloc(1, sizeof(*vt) + 64); /* slack for overflow */ /* simulate the UNCHECKED kernel write for entries[0..47] */ char *base = (char *)vt; for (uint32_t i = 0; i < count; i++) { char *ent = base + __builtin_offsetof(struct atom_voltage_table, entries[i]); *(uint16_t *)ent = (uint16_t)(0x1337); *(uint32_t *)(ent + 2) = 0; } printf("\nconcrete model (count=48, unchecked writes): entries[32..47] wrote %zu bytes " "past entries[] into adjacent struct memory\n", (size_t)(48-32) * sizeof(struct atom_voltage_table_entry)); printf(" -> in-kernel these bytes overwrite evergreen_power_info.cac_weights / " "powertune pointers -> corruption during si_dpm_enable\n"); free(vt); } if (any_oob) { printf("\nDF-1136: CONFIRMED heap OOB write past entries[32] (VBIOS count unbounded)\n"); } else { printf("\nDF-1136: NOT reproduced\n"); } /* ---- WITH FIX: clamp count to MAX_VOLTAGE_ENTRIES ---- */ printf("\n--- WITH FIX (clamp count to MAX_VOLTAGE_ENTRIES=32) ---\n"); int fix_oob = 0; for (size_t c = 0; c < sizeof(counts)/sizeof(counts[0]); c++) { uint32_t count = counts[c]; uint32_t fcount = count > MAX_VOLTAGE_ENTRIES ? MAX_VOLTAGE_ENTRIES : count; uint32_t oob = (fcount > MAX_VOLTAGE_ENTRIES) ? fcount - MAX_VOLTAGE_ENTRIES : 0; if (oob) fix_oob++; printf("VBIOS count=%-4u -> clamped count=%-4u OOB writes past entries[32]=%u %s\n", count, fcount, oob, oob ? ">>> STILL OOB" : "(in bounds)"); } printf("FIX result: %d OOB cases remain (expect 0)\n", fix_oob); printf("DF-1136 FIX: %s\n", fix_oob == 0 ? "VALIDATED - clamp prevents all overflows past entries[32]" : "INCOMPLETE"); return any_oob ? 0 : 1; } |