โฌข DragonFlyBSD Kernel Audit
DF-3612 / harness.c
โ† back to finding โ†“ download raw
/*
 * DF-3612 โ€” integer divide-by-zero from VBIOS spread-spectrum rate
 * (radeon si_dpm.c:4831 / 4931).  Same family as DF-1137/DF-1129 but a
 * distinct sink.
 *
 * radeon_atombios_get_asic_ss_info() (radeon_atombios.c:1543-1548) copies
 * `ss->rate = le16_to_cpu(ss_assign->v1.usSpreadRateInKhz)` straight from
 * the VBIOS ASIC_InternalSS_Info table and returns true โ€” with NO zero
 * check.  si_dpm.c then divides by it whenever the SS entry matches:
 *
 *   engine SS (si_calculate_sclk_params):
 *     si_dpm.c:4829   if (radeon_atombios_get_asic_ss_info(rdev, &ss,
 *                            ASIC_INTERNAL_ENGINE_SS, vco_freq)) {
 *     si_dpm.c:4831       u32 clk_s = reference_clock * 5 / (reference_divider * ss.rate);  // #DE
 *     si_dpm.c:4832       u32 clk_v = 4 * ss.percentage * fbdiv / (clk_s * 10000);          // #DE if clk_s==0
 *
 *   memory SS (si_populate_mclk_value):
 *     si_dpm.c:4931       u32 clks = reference_clock * 5 / ss.rate;                          // #DE
 *
 * Reached on every power-state conversion (si_convert_power_level_to_smc ->
 * si_populate_sclk_value / si_populate_mclk_value) when pi->sclk_ss /
 * pi->mclk_ss is set (VBIOS SS table present, rv770_get_engine_memory_ss).
 * On x86-64 the division compiles to `div` -> #DE -> kernel panic.
 *
 * Harness: [A] executes the exact expressions with ss.rate == 0 in forked
 * children (SIGFPE == kernel #DE panic).  [B] runs the guarded variant from
 * fix.diff.
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdint.h>

struct radeon_atom_ss { uint32_t percentage; uint32_t rate; uint32_t type; };

static volatile uint32_t sink;

/* radeon_atombios.c:1543-1548 โ€” no zero check on rate */
static int radeon_atombios_get_asic_ss_info(struct radeon_atom_ss *ss, uint16_t vbios_rate)
{
	ss->percentage = 2;	/* usSpreadSpectrumPercentage */
	ss->rate        = vbios_rate;
	return 1;		/* entry matched, "success" */
}

/* si_dpm.c:4825-4840 verbatim structure */
static void run_engine_ss(uint32_t reference_clock, uint32_t reference_divider,
			  uint16_t vbios_rate)
{
	struct radeon_atom_ss ss;
	uint32_t fbdiv = 0x1234;

	if (radeon_atombios_get_asic_ss_info(&ss, vbios_rate)) {
		uint32_t clk_s = reference_clock * 5 / (reference_divider * ss.rate); /* :4831 */
		uint32_t clk_v = 4 * ss.percentage * fbdiv / (clk_s * 10000);        /* :4832 */
		sink = clk_s + clk_v;
	}
	printf("    UNREACHABLE: division by zero survived\n");
}

/* si_dpm.c:4916-4940 verbatim structure */
static void run_memory_ss(uint32_t reference_clock, uint16_t vbios_rate)
{
	struct radeon_atom_ss ss;
	uint32_t memory_clock = 150000, freq_nom = memory_clock * 2;
	uint64_t tmp64 = freq_nom / reference_clock;	/* DF-3613 sink; nonzero here */
	uint64_t tmp = tmp64 * tmp64;

	if (radeon_atombios_get_asic_ss_info(&ss, vbios_rate)) {
		uint32_t clks = reference_clock * 5 / ss.rate;                        /* :4931 */
		uint32_t clkv = (uint32_t)((((131 * ss.percentage * ss.rate) / 100) * tmp) / freq_nom);
		sink = clks + clkv;
	}
	printf("    UNREACHABLE: division by zero survived\n");
}

/* fix.diff variant */
static int run_engine_ss_fixed(uint32_t reference_clock, uint32_t reference_divider,
			       uint16_t vbios_rate)
{
	struct radeon_atom_ss ss;
	uint32_t fbdiv = 0x1234;

	if (radeon_atombios_get_asic_ss_info(&ss, vbios_rate)) {
		if (ss.rate == 0)			/* fix.diff guard */
			return -1;
		uint32_t clk_s = reference_clock * 5 / (reference_divider * ss.rate);
		uint32_t clk_v = (4 * ss.percentage * fbdiv) / (clk_s * 10000);
		sink = clk_s + clk_v;
	}
	return 0;
}

static void report(int st)
{
	if (WIFSIGNALED(st))
		printf("    child died from SIGFPE (signal %d): integer divide by zero\n"
		       "    >>> In kernel: #DE fault -> panic during DPM power-state upload <<<\n",
		       WTERMSIG(st));
	else
		printf("    child exited %d (NOT expected)\n", WEXITSTATUS(st));
}

int main(void)
{
	uint32_t reference_clock = 27000, reference_divider = 2;
	pid_t p;
	int st = 0;

	printf("== DF-3612 harness: crafted VBIOS ASIC_InternalSS_Info with usSpreadRateInKhz == 0 ==\n\n");
	printf("radeon_atombios.c:1546: ss->rate = le16(vbios)  [no zero check], returns true\n");
	printf("reference_clock=%u reference_divider=%u\n\n", reference_clock, reference_divider);

	printf("[A1] engine SS: si_dpm.c:4831  clk_s = ref*5 / (ref_div * ss.rate)  with rate=0\n");
	fflush(stdout);
	p = fork();
	if (p == 0) { run_engine_ss(reference_clock, reference_divider, 0); _exit(0); }
	waitpid(p, &st, 0); report(st);

	printf("\n[A2] memory SS: si_dpm.c:4931  clks = ref*5 / ss.rate  with rate=0\n");
	fflush(stdout);
	p = fork();
	if (p == 0) { run_memory_ss(reference_clock, 0); _exit(0); }
	waitpid(p, &st, 0); report(st);

	printf("\n[B] Fixed path (fix.diff: `if (ss.rate == 0) return -EINVAL;` before use)\n");
	int rc = run_engine_ss_fixed(reference_clock, reference_divider, 0);
	printf("    rate=0 -> rc=%d (SS skipped, no trap, DPM continues without SS)\n", rc);
	rc = run_engine_ss_fixed(reference_clock, reference_divider, 30);
	printf("    rate=30 -> rc=0 (SS programmed normally)\n");
	return 0;
}