โฌข DragonFlyBSD Kernel Audit
DF-3742 / harness_refclock.c
โ† back to finding โ†“ download raw
/*
 * DF-3742 harness: zero PLL reference clock parsed unchecked -> kernel divide-by-zero (#DE)
 *
 * Parse side (the bug): radeon_atom_get_clock_info() stores the FirmwareInfo reference
 * clocks verbatim, with NO zero check โ€” sys/dev/drm/radeon/radeon_atombios.c:
 *    :1146-1147  p1pll->reference_freq = le16_to_cpu(firmware_info->info.usReferenceClock);
 *    :1188-1193  spll->reference_freq  = ... usReferenceClock / usCoreReferenceClock (DCE4)
 *    :1215-1220  mpll->reference_freq  = ... usReferenceClock / usMemoryReferenceClock (DCE4)
 * while pll_out_min == 0 IS defaulted (:1173-1178, :1202-1207, :1229-1234) โ€” the reference
 * clocks are not.
 *
 * Sink 1 (default modesetting, no DPM needed): radeon_compute_pll_avivo()
 *    radeon_display.c:1027   den = pll->reference_freq;
 *    radeon_display.c:1040   *fb_div = DIV_ROUND_CLOSEST(nom * *ref_div * post_div, den);
 * DIV_ROUND_CLOSEST(x, 0) is an integer division by zero -> SIGFPE on x86 (#DE -> kernel
 * panic with trap.
 *
 * Sink 2: atombios_crtc.c:1128-1132
 *    step_size = (4 * amount * ref_div * (rate*2048)) / (125 * 25 * pll->reference_freq / 100);
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>

typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32;

struct radeon_pll_model { u32 reference_freq; u32 pll_out_min; };

#define DIV_ROUND_CLOSEST(x, divisor)(				\
{								\
	typeof(x) __x = x;					\
	typeof(divisor) __d = divisor;				\
	(((typeof(x))-1) > 0 || (__x) > 0) ?			\
		((__x + (__d / 2)) / __d) :			\
		((__x - (__d / 2)) / __d);			\
}								\
)

static volatile sig_atomic_t got_fpe;
static void fpe_handler(int sig) { (void)sig; got_fpe = 1; _exit(42); }

int main(void)
{
	/* VBIOS FirmwareInfo with usReferenceClock = 0 */
	u16 usReferenceClock = 0;
	struct radeon_pll_model p1pll;

	/* verbatim parse store, radeon_atombios.c:1146-1148 */
	p1pll.reference_freq = usReferenceClock;
	/* contrast: pll_out_min IS defaulted when 0 (radeon_atombios.c:1173-1178) */
	p1pll.pll_out_min = 0; if (p1pll.pll_out_min == 0) p1pll.pll_out_min = 64800;

	printf("parsed: reference_freq=%u (NO default applied), pll_out_min=%u (defaulted)\n",
	       p1pll.reference_freq, p1pll.pll_out_min);

	setbuf(stdout, NULL);
	signal(SIGFPE, fpe_handler);

	/* sink: radeon_display.c:1027,1040 (radeon_compute_pll_avivo) */
	u32 nom = 25200, ref_div = 2, post_div = 1, fb_div_max = 0xFFFF;
	volatile u32 den = p1pll.reference_freq;		/* :1027 โ€” volatile: kernel really divides */
	u32 ref_div_max = 32, fb_div;
	ref_div_max = ref_div_max;
	/* avivo_get_fb_ref_div(), radeon_display.c:921-936 */
	u32 rdiv = den / post_div; if (rdiv < 1) rdiv = 1; if (rdiv > 32) rdiv = 32;
	printf("dividing by reference_freq=%u now (radeon_display.c:1040)...\n", den);
	volatile u32 q = nom * rdiv * post_div;
	fb_div = DIV_ROUND_CLOSEST(q, den);		/* :1040 โ€” den == 0 -> #DE */
	(void)fb_div_max; (void)fb_div;
	printf("unreachable: fb_div=%u\n", fb_div);

	printf("FAIL: no SIGFPE raised\n");
	return 1;
}