DF-4127 / rv6xx_clkv_div0.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | /* * rv6xx_clkv_div0.c โ harness for DF-4127 (rv6xx_dpm.c pass 2): * * rv6xx_calculate_spread_spectrum_clk_v() (sys/dev/drm/radeon/rv6xx_dpm.c:534-542) * divides by `5375 * ((vco_freq * 10) / (4096 >> fb_divider_scale))`. * The inner division TRUNCATES: for a small VCO (attacker-influenced via the * crafted-VBIOS firmware clock tables โ rdev->clock.mpll.reference_freq and * the power-table MCLK values that feed the ATOM divider computation) the * inner quotient is 0, making the whole divisor 0 -> u32 divide-by-zero * (kernel #DE -> panic; userland SIGFPE here). This is the rv6xx instance * of the "second-division truncation div0" family (DF-4120 cypress twin); * distinct from DF-1392 which covers zero SCLK and zero ss.rate. * * The sibling first division `vco_freq / ref_freq` (:538) traps the same * way when the passed ref_freq == 0 (reference_freq 0 in crafted firmware * info / ref_div+1 wrap); demonstrated as case B. * * The function below is VERBATIM (u32 == uint32_t, radeon types stripped). * Run: ./clkv_div0 {a|b} [-p] -p = proposed zero-divisor guard * Exit: 77 = SIGFPE taken (division by zero confirmed), 0 = guarded, 70 = err */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <signal.h> #include <unistd.h> typedef uint32_t u32; /* VERBATIM rv6xx_dpm.c:534-542 */ static inline u32 rv6xx_calculate_spread_spectrum_clk_v(u32 vco_freq, u32 ref_freq, u32 ss_rate, u32 ss_percent, u32 fb_divider_scale) { u32 fb_divider = vco_freq / ref_freq; return (ss_percent * ss_rate * 4 * (fb_divider * fb_divider) / (5375 * ((vco_freq * 10) / (4096 >> fb_divider_scale)))); } static int patched = 0; /* fixed variant: guard both divisions */ static inline u32 fixed_clk_v(u32 vco_freq, u32 ref_freq, u32 ss_rate, u32 ss_percent, u32 fb_divider_scale) { u32 inner, fb_divider; if (ref_freq == 0) return 0; fb_divider = vco_freq / ref_freq; inner = (vco_freq * 10) / (4096 >> fb_divider_scale); if (inner == 0) return 0; return (ss_percent * ss_rate * 4 * (fb_divider * fb_divider) / (5375 * inner)); } static void fpe_handler(int sig, siginfo_t *si, void *uc) { (void)uc; (void)sig; (void)si; printf("OOB confirmed: SIGFPE (integer divide by zero) in verbatim " "rv6xx_calculate_spread_spectrum_clk_v\n"); printf("RESULT: VULNERABLE โ divide-by-zero taken (kernel analog: #DE panic) (exit 77)\n"); _exit(77); } int main(int argc, char **argv) { struct sigaction sa; u32 r; if (argc < 2) { fprintf(stderr, "usage: %s {a|b} [-p]\n", argv[0]); return 70; } if (argc > 2 && !strcmp(argv[2], "-p")) patched = 1; memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = fpe_handler; sa.sa_flags = SA_SIGINFO; if (sigaction(SIGFPE, &sa, NULL)) { perror("sigaction"); return 70; } setvbuf(stdout, NULL, _IONBF, 0); { /* volatile: defeat constant folding of the division (gcc folds * literal 0/0 as UB); forces a real runtime division */ volatile u32 vvco = 200, vref = 10000, vrate = 30, vpct = 2, vscale = 1; volatile u32 zvco = 200, zref = 0; (void)vvco; (void)vref; (void)vrate; (void)vpct; (void)vscale; (void)zvco; (void)zref; if (argv[1][0] == 'a') { /* Case A (the DF-4120-twin "second division"): nonzero * ref_freq (10000 = 100 MHz in 10 kHz units, plausible mpll * reference), nonzero ss.rate (30) / ss.percentage (2) โ * DF-1392 conditions explicitly NOT used โ but small VCO * (200 * 10 kHz = 2 MHz), fb_divider_scale = 1 (RV670+): * (200*10)/(4096>>1) = 2000/2048 = 0 -> divisor 0. */ printf("[DF-4127 case A] vco=%u ref=%u ss_rate=%u ss_pct=%u scale=1 -> " "inner (vco*10)/2048 = %u\n", 200u, 10000u, 30u, 2u, (200u * 10u) / 2048u); r = patched ? fixed_clk_v(vvco, vref, vrate, vpct, vscale) : rv6xx_calculate_spread_spectrum_clk_v(vvco, vref, vrate, vpct, vscale); printf("clk_v = %u (no trap)\n", r); } else { /* Case B (sibling first division at :538): ref_freq == 0 */ printf("[DF-4127 case B] ref_freq = 0 -> vco/ref_freq traps at rv6xx_dpm.c:538\n"); r = patched ? fixed_clk_v(zvco, zref, vrate, vpct, vscale) : rv6xx_calculate_spread_spectrum_clk_v(zvco, zref, vrate, vpct, vscale); printf("clk_v = %u (no trap)\n", r); } } if (patched) printf("RESULT: OK โ zero-divisor guard returned without trapping\n"); return 0; } |