DF-1137 / harness.c
/* * DF-1137 harness: radeon/si_dpm.c si_thermal_setup_fan_table divide-by-zero * * This is a userspace logic harness (the actual bug is in radeon.ko which is * only loaded when AMD SI hardware is present; the QEMU audit guest has no AMD * GPU so radeon.ko is never kldload'ed at runtime). The harness demonstrates * the divisor reaches zero with BIOS-controlled u16 fan-table inputs that are * never validated before being used as divisors. * * Reproduces the integer division that traps as #DE in kernel context. * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <signal.h> #include <setjmp.h> #include <string.h> /* Mirror of struct radeon_fan_info fields as filled by r600_dpm.c from VBIOS: * fan.t_min = le16_to_cpu(fan_info->fan.usTMin); r600_dpm.c:897 * fan.t_med = le16_to_cpu(fan_info->fan.usTMed); r600_dpm.c:898 * fan.t_high= le16_to_cpu(fan_info->fan.usTHigh); r600_dpm.c:899 */ struct fan { uint16_t t_min, t_med, t_high, pwm_min, pwm_med, pwm_high; }; /* Faithful copy of si_dpm.c:6046-6064 math. * duty100 is computed at runtime from a MMIO register (CG_FDO_CTRL1); we use * a typical non-zero value (50) to isolate the t_diff divisor. */ static void si_thermal_setup_fan_table_buggy(struct fan *fan, uint32_t duty100) { int32_t t_diff1, t_diff2; int32_t pwm_diff1, pwm_diff2; int32_t slope1, slope2; /* si_dpm.c:6057-6058 -- NO validation */ t_diff1 = fan->t_med - fan->t_min; t_diff2 = fan->t_high - fan->t_med; pwm_diff1 = fan->pwm_med - fan->pwm_min; pwm_diff2 = fan->pwm_high - fan->pwm_med; /* si_dpm.c:6063-6064 -- divide-by-zero if t_diff1 or t_diff2 == 0 */ slope1 = (int32_t)((50 + ((16 * duty100 * pwm_diff1) / t_diff1)) / 100); slope2 = (int32_t)((50 + ((16 * duty100 * pwm_diff2) / t_diff2)) / 100); printf(" buggy: slope1=%d slope2=%d\n", slope1, slope2); } /* Faithful copy of the fix: validate deltas before use */ static int si_thermal_setup_fan_table_fixed(struct fan *fan, uint32_t duty100) { int32_t t_diff1, t_diff2; t_diff1 = fan->t_med - fan->t_min; t_diff2 = fan->t_high - fan->t_med; if (t_diff1 == 0 || t_diff2 == 0) { printf(" fixed: REJECTED zero delta (t_min=%u t_med=%u t_high=%u) -> -EINVAL\n", fan->t_min, fan->t_med, fan->t_high); return -1; } si_thermal_setup_fan_table_buggy(fan, duty100); return 0; } static sigjmp_buf jb; static void fpe_handler(int s) { (void)s; siglongjmp(jb, 1); } int main(void) { struct sigaction sa = { .sa_handler = fpe_handler }; struct fan crafted_vbios = { .t_min = 5000, .t_med = 5000, .t_high = 9000, .pwm_min = 100, .pwm_med = 200, .pwm_high = 300 }; uint32_t duty100 = 50; sigaction(SIGFPE, &sa, NULL); printf("=== DF-1137 harness: crafted VBIOS with usTMed==usTMin ===\n"); printf("attempting buggy version (mirrors si_dpm.c:6046-6064)...\n"); if (sigsetjmp(jb, 1) == 0) { si_thermal_setup_fan_table_buggy(&crafted_vbios, duty100); printf(" (no trap -- unexpected)\n"); } else { printf(" buggy: SIGFPE / #DE divide-by-zero raised!\n"); printf(" >>> In kernel context this is a kernel panic. <<<\n"); } printf("\nattempting fixed version (mirrors fix.diff)...\n"); si_thermal_setup_fan_table_fixed(&crafted_vbios, duty100); printf("\nfixed version with benign VBIOS (t_min<t_med<t_high)...\n"); struct fan good = { .t_min = 5000, .t_med = 7000, .t_high = 9000, .pwm_min = 100, .pwm_med = 200, .pwm_high = 300 }; si_thermal_setup_fan_table_fixed(&good, duty100); return 0; } |