⬢ DragonFlyBSD Kernel Audit
DF-3587 / harness.c
← back to finding ↓ download raw
/*
 * 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;
}