⬢ DragonFlyBSD Kernel Audit
DF-3647 / harness.c
← back to finding ↓ download raw
/*
 * ci_primitives_harness.c — userland proof-of-primitive harness for the
 * source findings in DragonFlyBSD sys/dev/drm/radeon/ci_dpm.c (pass 2).
 *
 * It models, with the exact types used in the kernel:
 *   [A] DF-3644: ci_get_svi2_voltage_table() copies a VBIOS dependency count
 *       (u8, up to 255) into struct atom_voltage_table.entries[32] BEFORE the
 *       si_trim_voltage_table_to_fit_state_table() call -> OOB write geometry.
 *   [B] DF-3647: ci_calculate_mclk_params()/ci_calculate_sclk_params() divide
 *       by ss.rate which radeon_atombios_get_asic_ss_info() copies raw from
 *       the VBIOS (radeon_atombios.c:1546) -> div-by-zero -> SIGFPE.
 *   [C] DF-3648: ci_calculate_mclk_params() divides freq_nom by
 *       rdev->clock.mpll.reference_freq stored unchecked from the VBIOS
 *       (radeon_atombios.c:1216-1221) -> div-by-zero -> SIGFPE.
 *   [D] DF-3649: ci_get_std_voltage_value_sidd() computes
 *       idx = (u16)(cac_leakage_table.count - 1) with count==0 (entries is
 *       ZERO_LENGTH_PTR == (void*)-8, non-NULL, passes the guard) -> index
 *       65535 -> wild read address.
 *   [E] DF-3650: the VCE fill loop indexes clockInfo[clk_idx * ucEntrySize]
 *       with clk_idx = ucClockInfoIndex & 0x3f (<=63) and ucEntrySize a raw
 *       u8, with NO ucNumEntries bound -> byte-offset geometry.
 *
 * Build: cc -O2 -o harness ci_primitives_harness.c
 * Run:   ./harness          (survives; prints geometry + child exit codes)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.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];
};
struct dep_entry { uint32_t clk; uint32_t v; };
struct dep_table { uint32_t count; struct dep_entry entries[255]; };

/* canary placed immediately after a truncated (stack) voltage table to
 * emulate the next field of struct ci_power_info (vddci_voltage_table) */
struct pi_slice {
	struct atom_voltage_table vddc;      /* the overflow source   */
	unsigned char canary[64];            /* stands in for the rest */
};

/* [A] exact copy loop of ci_get_svi2_voltage_table (ci_dpm.c:2159-2163) */
static void get_svi2(struct dep_table *dep, struct atom_voltage_table *vt)
{
	uint32_t i;
	vt->mask_low = 0; vt->phase_delay = 0;
	vt->count = dep->count;
	for (i = 0; i < vt->count; i++) {
		vt->entries[i].value = (uint16_t)dep->entries[i].v;
		vt->entries[i].smio_low = 0;
	}
}

static int expect_fpe(void (*fn)(void))
{
	pid_t p = fork();
	if (p == 0) { fn(); _exit(0); }
	int st = 0; waitpid(p, &st, 0);
	if (WIFSIGNALED(st) && WTERMSIG(st) == SIGFPE)
		return 1;                       /* integer div-by-zero faulted */
	return 0;
}

static volatile uint32_t g_sink;
static volatile uint32_t g_zero = 0;   /* runtime zero: VBIOS-supplied value */
static void div_ss_rate(void)   { uint32_t x = 100 * 5 / g_zero; g_sink = x; }
static void div_ref_clock(void) { uint32_t x = 300000 / g_zero; g_sink = x; }

int main(void)
{
	/* ---------- [A] DF-3644: entries[32] overflow geometry ---------- */
	struct pi_slice *pi = calloc(1, sizeof(*pi));
	struct dep_table *dep = calloc(1, sizeof(*dep));
	memset(pi->canary, 0xAA, sizeof(pi->canary));
	dep->count = 200;                       /* crafted VBIOS u8 count */
	for (uint32_t i = 0; i < dep->count; i++)
		dep->entries[i] = (struct dep_entry){ .clk = 50000 + i * 1000,
		                                      .v   = 900 + i };  /* u16 voltages */
	get_svi2(dep, &pi->vddc);               /* ci_dpm.c:2160 loop */
	size_t overflow_bytes = (dep->count > MAX_VOLTAGE_ENTRIES)
		? (dep->count - MAX_VOLTAGE_ENTRIES) * sizeof(struct atom_voltage_table_entry) : 0;
	int canary_hit = memcmp(pi->canary, "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA", 8) != 0;
	printf("[A] DF-3644: count=%u entries_cap=%d -> OOB write of %zu bytes past entries[32]; "
	       "bytes past end of struct atom_voltage_table: %s; canary clobbered: %s\n",
	       dep->count, MAX_VOLTAGE_ENTRIES, overflow_bytes,
	       overflow_bytes > 64 ? "YES (canary/adjacent memory)" : "marginal",
	       canary_hit ? "YES" : "no");
	free(pi); free(dep);

	/* ---------- [B] DF-3647: ss.rate == 0 division ---------- */
	printf("[B] DF-3647: 100*5/ss.rate(0)  -> %s\n",
	       expect_fpe(div_ss_rate) ? "SIGFPE (kernel: #DE panic)" : "no fault (unexpected)");

	/* ---------- [C] DF-3648: reference_freq == 0 division ---------- */
	printf("[C] DF-3648: freq_nom/ref(0)   -> %s\n",
	       expect_fpe(div_ref_clock) ? "SIGFPE (kernel: #DE panic)" : "no fault (unexpected)");

	/* ---------- [D] DF-3649: idx = (u16)(count-1), count==0 ---------- */
	uint32_t cac_count = 0;
	uint16_t idx = (uint16_t)(cac_count - 1);          /* ci_dpm.c:2382 */
	union cac_entry { struct { uint16_t vddc; uint32_t leak; } a; };
	union cac_entry *entries = (union cac_entry *)(uintptr_t)0xFFFFFFFFFFFFFFF8UL; /* ZERO_LENGTH_PTR (kern_slaballoc.c:193) */
	uint64_t addr = (uint64_t)(uintptr_t)&entries[idx];
	printf("[D] DF-3649: count=0 -> idx=%u; &entries[idx] = 0x%llx -> %s\n",
	       idx, (unsigned long long)addr,
	       "unmapped kernel address (wild read -> page fault / panic)");

	/* ---------- [E] DF-3650: VCE clk_idx byte-offset geometry ---------- */
	uint8_t clk_idx = 63 & 0x3f;               /* r600_dpm.c:1127 mask */
	uint8_t entry_size = 255;                  /* raw u8 from VBIOS     */
	uint32_t num_entries = 1;                  /* crafted tiny array    */
	printf("[E] DF-3650: clk_idx=%u * entrySize=%u = %u bytes past clockInfo[0]"
	       " while ucNumEntries=%u (unchecked, ci_dpm.c:5656-5658 vs :5637)\n",
	       clk_idx, entry_size, (unsigned)(clk_idx * entry_size), num_entries);

	return 0;
}