DF-1401 / trinity_nonclock_oob.c
/* * DF-1401 harness: trinity_parse_power_table nonClockInfoIndex OOB read. * * Replicates the EXACT vulnerable logic from: * sys/dev/drm/radeon/trinity_dpm.c:1774-1776 * non_clock_array_index = power_state->v2.nonClockInfoIndex; // u8 from VBIOS, NO CHECK * non_clock_info = (struct _ATOM_PPLIB_NONCLOCK_INFO *) * &non_clock_info_array->nonClockInfo[non_clock_array_index]; // OOB read * * Contrast the CLOCK path at :1786-1790 which DOES guard: * idx = (u8 *)&power_state->v2.clockInfoIndex[0]; * for (j ...) { * clock_array_index = idx[j]; * if (clock_array_index >= clock_info_array->ucNumEntries) continue; // guarded * ... * } * The non-clock index has no such check. * * With a crafted VBIOS where power_state->v2.nonClockInfoIndex (u8, 0..255) >= * ucNumEntries, the array index reads out of bounds. ucNumEntries=1 + index=255 * reads 255 entries past the single-entry array. non_clock_info is then passed * to trinity_parse_pplib_non_clock_info() which reads its fields (clock flags, * etc.) -> OOB read of VBIOS/adjacent memory; stored into power state. * * This is a READ-ONLY primitive (the OOB data is read and stored, not written * through), so there is no escalation chain — the impact ceiling is OOB-info * read / wrong DPM settings (potential DoS via bogus clock programming). The * QEMU guest has no AMD Trinity GPU, so it is unreachable live here. * * Build: cc -O2 -o trinity_nonclock_oob trinity_nonclock_oob.c * Run: ./trinity_nonclock_oob */ #include <stdio.h> #include <string.h> #include <stdint.h> #define MAX_STATES 16 struct _ATOM_PPLIB_NONCLOCK_INFO { uint32_t flags; uint32_t entry_size_dummy[3]; }; struct _NonClockInfoArray { uint8_t ucNumEntries; uint8_t ucEntrySize; struct _ATOM_PPLIB_NONCLOCK_INFO nonClockInfo[1]; /* VLA in reality */ }; struct pplib_power_state_v2 { uint8_t nonClockInfoIndex; /* from VBIOS, unchecked */ uint8_t ucNumDPMLevels; }; int main(void) { /* A small VBIOS image region with a 1-entry nonClockInfoArray, followed by * adjacent (attacker-uncontrolled-in-real-life) VBIOS bytes. */ uint8_t bios[256]; memset(bios, 0xEE, sizeof(bios)); /* poison so OOB reads are visible */ struct _NonClockInfoArray *arr = (struct _NonClockInfoArray *)bios; arr->ucNumEntries = 1; arr->ucEntrySize = sizeof(struct _ATOM_PPLIB_NONCLOCK_INFO); arr->nonClockInfo[0].flags = 0x12345678; /* Crafted power state: nonClockInfoIndex far past ucNumEntries. */ struct pplib_power_state_v2 ps; ps.nonClockInfoIndex = 200; /* u8, unchecked -> 200 vs 1 entry */ ps.ucNumDPMLevels = 0; printf("DF-1401 trinity nonClockInfoIndex OOB-read demonstration\n"); printf("nonClockInfoArray.ucNumEntries = %u\n", arr->ucNumEntries); /* ---- exact kernel logic (trinity_dpm.c:1774-1776) ---- */ uint8_t non_clock_array_index = ps.nonClockInfoIndex; /* line 1774: NO CHECK */ struct _ATOM_PPLIB_NONCLOCK_INFO *non_clock_info = &arr->nonClockInfo[non_clock_array_index]; /* line 1775-1776: OOB */ int oob = (non_clock_array_index >= arr->ucNumEntries); printf("nonClockInfoIndex (from VBIOS) = %u\n", non_clock_array_index); printf("guard present? : %s\n", "NO (contrast clock path at :1789 which checks >= ucNumEntries)"); printf("reads entry [%u] of a %u-entry array -> %s\n", non_clock_array_index, arr->ucNumEntries, oob ? "OUT OF BOUNDS" : "in bounds"); printf("OOB bytes past array start read: ~%lu\n", (unsigned long)(non_clock_array_index * arr->ucEntrySize)); printf("OOB-read flags value stored into power state: 0x%08x\n", non_clock_info->flags); if (oob) { printf("\nOOB READ CONFIRMED: nonClockInfoIndex=%u used against ucNumEntries=%u with no " "bounds check; reads ~%lu bytes OOB from VBIOS into the power-state struct.\n", non_clock_array_index, arr->ucNumEntries, (unsigned long)(non_clock_array_index * arr->ucEntrySize)); printf("READ-ONLY primitive -> no escalation; impact = wrong DPM settings / OOB info " "read (DoS via bogus clock programming on crafted VBIOS).\n"); return 0; } fprintf(stderr, "ERROR: OOB not observed\n"); return 1; } |