Divide-by-zero kernel panic in rv730_populate_sclk_value: do_div by verbatim VBIOS reference clock (usReferenceClock==0) β rv730 twin of DF-1992
Summary
The unconditional per-engine-clock-level divide uses spll.reference_freq copied verbatim from VBIOS FirmwareInfo.usReferenceClock with no zero check (unlike the pll_out_min fallbacks nearby) - unlike DF-1937's SS-block divides it needs no SS table match, and DPM is on by default. Verified via verbatim-replica harness on guest (baseline zero -> trap-class exit 132 with objdump-confirmed ud2 lowering; fixed -> -EINVAL clean); fix compiled into the real radeon.ko -Werror. VBIOS-control threat model (SPI reflash/VFIO romfile); twin of accepted DF-1992.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4857 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| sclkdiv0.c | trigger | verbatim userland replica of rv730_populate_sclk_value (rv730_dpm.c:40-117) | 7.7 KB | view raw |
| build.sh | build-script | cc -O2 -Wall; second binary with -DFIX_DF4857 | 65 B | view raw |
| run.sh | run-script | baseline ok/zero + fixed ok/zero | 183 B | view raw |
| run.log | run-log | decisive guest run: zero -> exit=132 fatal trap; fixed -> exit=0 | 939 B | view raw |
| fix.diff | suggested-fix | zero-guard before do_div (twin of accepted DF-1992 fix) | 448 B | view raw |
| radeon_fix_apply.log | fix-build-log | both fixes applied to guest /usr/src | 79 B | view raw |
| radeon_module_build.log | fix-build-log | radeon.ko rebuilt under -Werror, link OK, then reverted (md5 verified) | 6.8 KB | view raw |
| VERDICT.md | verdict | full narrative | 3.9 KB | β raw |
| env.txt | env | guest uname/cc/source-hash | 758 B | view raw |
DF-4857 VERDICT
Finding: do_div(tmp, reference_clock) at sys/dev/drm/radeon/rv730_dpm.c:71 divides by
rdev->clock.spll.reference_freq, which is copied verbatim from VBIOS
FirmwareInfo.usReferenceClock (radeon_atombios.c:1189-1193) with no zero
check. Crafted/corrupt VBIOS with usReferenceClock==0 β fatal kernel trap
(divide error; on gcc 8.3 an ud2 UB-guard β invalid opcode) on the DPM-enable
path for RV730/RV710.
Status: REPRODUCED (logic level, verbatim-replica harness on the audit
guest β the kernel-level trigger is hardware-gated: the guest has no Radeon
GPU, so rv730_populate_sclk_value cannot be entered in-kernel there; same
convention as DF-4492/DF-1992/DF-1937 for this driver family).
Impact: panic (pure DoS; no memory-corruption primitive β the divisor is the only attacker input into the expression and the trap fires before any memory write). No escalation chain exists for this bug class.
How it was proven
- Reachability trace (source):
radeon_device_initβradeon_pm_init(dpm default on) βradeon_dpm_enableβrv770_dpm_enable(rv770_dpm.c:1956rv770_init_smc_table; :1212 dispatch for RV730/RV710) andrv770_convert_power_level_to_smc(rv770_dpm.c:636) βrv730_populate_sclk_value. The division at :71 executes unconditionally for every engine-clock level β before the SS block at :91-107 (which is where DF-1937's ss.rate/clk_s divides live; DF-1937 explicitly does NOT cover :71). - Unvalidated input (source):
spll->reference_freq = le16_to_cpu(firmware_info->info.usReferenceClock)(radeon_atombios.c:1192; DCE4 variant :1189-1190) β no zero check anywhere, while the same parser adds fallbacks for zeropll_out_min(:1173-1178, :1202-1207, :1229-1234), proving zero fields occur in real/corrupt images. - Executable proof (guest):
sclkdiv0.creplicates rv730_dpm.c:40-117 verbatim (register macros from rv730d.h:26-43, kernel-styledo_div). - modeok(usReferenceClock=2700): completes, fbdiv=18204444 fitsSPLL_FB_DIV_MASKβ control. - modezero(usReferenceClock=0): fatal trap βIllegal instruction (core dumped), exit=132. Guest cc is gcc 8.3, which lowers the UB zero-division toud2(confirmed via objdump in the transcript); adivqlowering yields #DE instead. In-kernel either is a hard panic (Fatal trap 6/17 ... in kernel mode).
Fix validation
fix.diff(guardif (reference_clock == 0) return -EINVAL;before :71 β identical in shape to the accepted DF-1992 fix for the rv740 twin) applied to the guest/usr/srctogether with DF-4858's fix;radeon.korebuilt under -Werror: compile+link OK (radeon_module_build.log,radeon_fix_apply.log).- Harness with
-DFIX_DF4857: modezeroreturns-EINVALcleanly, no division, exit=0 (run.log). - Guest
/usr/srcreverted afterwards;md5 rv730_dpm.cverified back to the pristined993b8a12e7cc99729a7d82f9685be95.
Boundaries checked (no escalation)
- The only attacker-controlled value in the expression is the divisor; the
quotient
fbdivis written into a fixedRV770_SMC_SCLK_VALUEstruct (fixed-size fields,cpu_to_be32), not a variable-length buffer β no OOB. fbdiv > SPLL_FB_DIV_MASK(26 bits) from a crafted engine_clock would spill OR-bits into upper fields ofCG_SPLL_FUNC_CNTL_3(register misprogram, hardware-functional only; upstream Linux identical) β hardening note, not a memory-safety bug; folded into the finding notes rather than filed separately.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedfixed: fix.diff (zero-guard, twin of the accepted DF-1992 fix) applied to guest /usr/src together with DF-4858's one-liner; radeon.ko rebuilt under -Werror with compile+link OK (radeon_module_build.log); harness baseline mode zero traps (exit=132) vs harness_fixed mode zero returning -EINVAL cleanly (exit=0, run.log); guest source then reverted (md5 re-verified pristine).
fix.diff; radeon_fix_apply.log; radeon_module_build.log; run.log (fixed sections); VERDICT.md
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (decisive: baseline mode zero -> 'Illegal instruction (core dumped)' exit=132; fixed mode zero -> 'FIX_GUARD: reference_clock==0 rejected (rv=-EINVAL)' exit=0; ok-mode control fbdiv=18204444); sclkdiv0.c (verbatim replica); radeon_fix_apply.log + radeon_module_build.log (fix compiles, radeon.ko links under -Werror); VERDICT.md
PoC changes
Authored fresh (no seed): verbatim replica of the sclk divider math with rv730d.h macros verbatim; radeon_atom_get_clock_dividers stubbed (firmware-side). Two build iterations: (1) 0x0000_0115 digit separator is invalid C (gcc error) -> plain hex; (2) sh job-status stderr interleaved in the log -> 'exec 2>&1' in run.sh for ordered capture. Also verified the trap mechanism (gcc 8.3 emits ud2 for UB zero-division) via objdump so the exit=132 (SIGILL) vs #DE discrepancy vs DF-1992's expected text is documented.
Verified recommended fix
In rv730_populate_sclk_value, reject a zero reference clock before the divider: if (reference_clock == 0) return -EINVAL; ahead of do_div(tmp, reference_clock) at rv730_dpm.c:71 (and seed spll/mpll reference_freq with a sane default in radeon_atombios.c when usReferenceClock==0, as already done for pll_out_min).
Verdict
Reproduced at logic level with a verbatim userland replica of rv730_populate_sclk_value (rv730_dpm.c:40-117, register macros from rv730d.h, kernel-style do_div) run on the audit guest; the kernel-level trigger is hardware-gated (no RV730 GPU in the guest), per the DF-4492/DF-1937 convention for this driver family. The division at rv730_dpm.c:71 is unconditional for every engine-clock level and divides by rdev->clock.spll.reference_freq, which radeon_atombios.c:1189-1193 copies verbatim from VBIOS FirmwareInfo.usReferenceClock with no zero check (the same parser adds fallbacks for zero pll_out_min at :1173/:1202/:1229). Replica mode zero (usReferenceClock=0) dies with a fatal trap ('Illegal instruction (core dumped)', exit=132 - guest gcc 8.3 lowers the UB zero-division to ud2, confirmed by objdump; a divq lowering yields #DE instead); in-kernel either is a hard panic on the DPM-enable path (radeon_dpm_enable -> rv770_dpm_enable -> rv770_init_smc_table rv770_dpm.c:1212 / rv770_convert_power_level_to_smc rv770_dpm.c:636) on RV730/RV710 with dpm on by default. Distinct from DF-1937 (same file L97-174: ss.rate==0/clk_s==0 inside the SS block, which additionally requires an SS table match) and the exact twin of accepted DF-1992 (rv740_dpm.c:145). Pure DoS: the divisor is the only attacker input; no memory-corruption primitive, no escalation chain. Fix validated: guard + fix.diff applied to guest /usr/src (with DF-4858's fix), radeon.ko rebuilt under -Werror (link OK), harness_fixed returns -EINVAL cleanly on mode zero.
No comments yet.