DF-3742 / harness_refclock.c
/* * 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; } |