DF-3587 / harness.c
/* * DF-3587 harness -- wild OOB kernel read in btc_find_voltage() * (sys/dev/drm/amd/amdgpu/si_dpm.c:3206-3215) * * btc_find_voltage() returns table->entries[table->count - 1].value when the * requested voltage exceeds every entry. With a BIOS that yields count == 0 * (no VDDCI/VDDC voltage object parsed, or a GPIO LUT object with * ucGpioEntryNum == 0 -- amdgpu_atombios.c:1539 only rejects > 32), the index * is (u32)(0 - 1) = 4294967295 and the kernel computes * &entries[0] + 4294967295 * sizeof(entry) * = &entries[0] + 0x7FFFFFFF8 (~34 GB offset) * => guaranteed unmapped wild read => page fault / panic. * * Reachable: si_apply_state_adjust_rules (si_dpm.c:3626-3631) * -> btc_apply_voltage_delta_rules (si_dpm.c:3312-3334) * -> btc_find_voltage (si_dpm.c:3214) * on every power-state recompute after dpm enable, with BIOS-controlled * power-state vddc/vddci values satisfying (vddc - vddci) > vddc_vddci_delta * (or the vddc branch with vddci - vddc > delta). */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> 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[32]; /* MAX_VOLTAGE_ENTRIES */ }; /* verbatim rename of btc_find_voltage() tail (si_dpm.c:3210-3214) */ static uint16_t btc_find_voltage_rep(struct atom_voltage_table *table, uint16_t voltage) { unsigned int i; for (i = 0; i < table->count; i++) if (voltage <= table->entries[i].value) return table->entries[i].value; return table->entries[table->count - 1].value; /* si_dpm.c:3214 */ } int main(void) { struct atom_voltage_table *t = calloc(1, sizeof(*t)); /* count == 0 */ uint16_t got; setvbuf(stdout, NULL, _IONBF, 0); printf("== DF-3587 harness: btc_find_voltage wild read on count==0 ==\n"); printf("kernel: sys/dev/drm/amd/amdgpu/si_dpm.c:3214 (entries[count-1], count u32)\n\n"); printf("[1] table->count = %u (BIOS: no VDDCI object / GPIO LUT with 0 entries)\n", t->count); printf("[2] index = (u32)(count - 1) = %u\n", (uint32_t)(t->count - 1)); printf("[2] byte offset = %u * %zu = 0x%llx (~%llu GB past the table)\n", (uint32_t)(t->count - 1), sizeof(struct atom_voltage_table_entry), (unsigned long long)((uint64_t)(t->count - 1) * sizeof(struct atom_voltage_table_entry)), (unsigned long long)((uint64_t)(t->count - 1) * sizeof(struct atom_voltage_table_entry) >> 30)); fflush(stdout); pid_t pid = fork(); if (pid == 0) { got = btc_find_voltage_rep(t, 1000); /* must fault: wild read */ printf("[3] child: read returned 0x%x without fault (unexpected)\n", got); _exit(0); } int st; waitpid(pid, &st, 0); if (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV) { printf("[3] child killed by SIGSEGV => kernel-equivalent wild read -> page fault\n"); printf("[4] RESULT: UNCHECKED count==0 -> WILD OOB READ CONFIRMED (kernel: panic)\n"); free(t); return 0; } printf("[4] child exited without SIGSEGV (unexpected)\n"); free(t); return 1; } |