β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4857

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4857 Β· 9 files
FileTypeDescriptionSize
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
VERDICT.md verdict full narrative
↓ download 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

  1. Reachability trace (source): radeon_device_init β†’ radeon_pm_init (dpm default on) β†’ radeon_dpm_enable β†’ rv770_dpm_enable (rv770_dpm.c:1956 rv770_init_smc_table; :1212 dispatch for RV730/RV710) and rv770_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).
  2. 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 zero pll_out_min (:1173-1178, :1202-1207, :1229-1234), proving zero fields occur in real/corrupt images.
  3. Executable proof (guest): sclkdiv0.c replicates rv730_dpm.c:40-117 verbatim (register macros from rv730d.h:26-43, kernel-style do_div). - mode ok (usReferenceClock=2700): completes, fbdiv=18204444 fits SPLL_FB_DIV_MASK β€” control. - mode zero (usReferenceClock=0): fatal trap β€” Illegal instruction (core dumped), exit=132. Guest cc is gcc 8.3, which lowers the UB zero-division to ud2 (confirmed via objdump in the transcript); a divq lowering yields #DE instead. In-kernel either is a hard panic (Fatal trap 6/17 ... in kernel mode).

Fix validation

  • fix.diff (guard if (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/src together with DF-4858's fix; radeon.ko rebuilt under -Werror: compile+link OK (radeon_module_build.log, radeon_fix_apply.log).
  • Harness with -DFIX_DF4857: mode zero returns -EINVAL cleanly, no division, exit=0 (run.log).
  • Guest /usr/src reverted afterwards; md5 rv730_dpm.c verified back to the pristine d993b8a12e7cc99729a7d82f9685be95.

Boundaries checked (no escalation)

  • The only attacker-controlled value in the expression is the divisor; the quotient fbdiv is written into a fixed RV770_SMC_SCLK_VALUE struct (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 of CG_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

fixed
baseline reproduced→ patch + rebuild →patched clean

fixed: 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
↓ fix.diffper-fix-DF-4857

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.