Divide-by-zero kernel panic via unvalidated VBIOS clock/spread-spectrum divisors in rv740_populate_sclk_value / rv740_populate_mclk_value
- File:
sys/dev/drm/radeon/rv740_dpm.c - Lines: 145, 165β166 (sclk); 258β260 (mclk); cf. source at
radeon_atombios.c:1192,1546 - Severity: Low
- CVSS 3.1:
CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U:C:N/I:N/A:H - CWE: CWE-369 Divide By Zero
- Confidence: likely
- Status: new
Summary
rv740_populate_sclk_value and rv740_populate_mclk_value perform integer
division by values that originate directly from the GPU VBIOS (AtomBIOS
firmware tables) with no zero-guard.
do_div(tmp, reference_clock)at:145divides byrdev->clock.spll.reference_freq- the spread-spectrum sub-blocks at
:165-166and:258-260divide byss.rateand by the derivedclk_s
β¦all of which can be zero when the VBIOS reports a malformed/crafted
firmware_info or ASIC_InternalSS_Info table.
A zero divisor raises #DE in kernel mode and panics the system. The DPM
table build runs automatically at device init and again on every root-driven
power-state transition, so a crafted VBIOS yields a reliable, repeatable
denial of service.
Root cause
The divisors are VBIOS-derived and never validated for zero:
reference_clock=rdev->clock.spll.reference_freq, read verbatim from the VBIOSfirmware_info.usReferenceClockatradeon_atombios.c:1192(spll->reference_freq = le16_to_cpu(firmware_info->info.usReferenceClock);) with no zero check; it is then used unguarded as thedo_divbase atrv740_dpm.c:145(do_div(tmp, reference_clock);).
do_div expands to ((uint64_t)(n)) / __base
(sys/dev/drm/include/linux/math64.h:36-42), a runtime div instruction
that faults on base==0.
-
ss.rateis read verbatim from the VBIOSASIC_InternalSS_Infotable atradeon_atombios.c:1546(ss->rate = le16_to_cpu(ss_assign->v1.usSpreadRateInKhz);) with no zero check; it then appears as a factor inclk_s = reference_clock * 5 / (reference_divider * ss.rate);atrv740_dpm.c:165(engine) and:258(memory). Ifss.rate==0the product in the denominator is0β#DE. -
Even when
ss.rate != 0,clk_sitself is the result of an integer division that yields0wheneverreference_clock * 5 < reference_divider * ss.rate(engine,:165) or< decoded_ref * ss.rate(memory,:258); that0is then used as the divisor inclk_v = 4 * ss.percentage * fbdiv / (clk_s * 10000);(:166) andclk_v = 0x40000 * ss.percentage * (...) / (clk_s * 10000);(:259-260), again producing#DE.
Note the mclk path already guards decoded_ref==0 by returning -EINVAL at
:256-257, which proves the author knew these divisors can be hostile but did
not extend the same guard to reference_clock, ss.rate, or clk_s.
Threat model
Attacker position: a malicious or corrupt GPU VBIOS image (crafted
firmware_info table with usReferenceClock==0, or ASIC_InternalSS_Info
entry with usSpreadRateInKhz==0 / a rate large enough to make clk_s==0).
This is reachable in the firmware-load / VM-GPU-passthrough threat model where the VBIOS is supplied by a host/hypervisor or attacker-controlled ROM, as well as via a malicious PCIe card.
Trigger path:
radeon driver attach β rv770_dpm.c rv770_parse_power_table /
rv770_setup_default_state β rv740_populate_sclk_value /
rv740_populate_mclk_value (callers at rv770_dpm.c:633 and :657,
cypress_dpm.c:695) during initial SMC state-table construction, and again
whenever a privileged user writes power_dpm_force_performance_level /
power_dpm_state sysfs nodes (rv770_dpm.c, radeon_pm.c:667-668,
S_IWUSR = root-only).
No privilege is gained; impact is a hard kernel panic (system-wide DoS)
on a machine bearing an RV740/Juniper (or Evergreen/Cypress/Northern-Islands
ASIC reusing these helpers via cypress_dpm.c:560-577 and
ni_dpm.c:2241-2258) radeon GPU.
Not reachable from an unprivileged local account: the sysfs writers are
0644-owner(root) and the clocks/dividers themselves are VBIOS-defined, not
user-injectable.
Proof of concept
This is a firmware-driven DoS, not a memory-corruption primitive, so the PoC is a crafted VBIOS rather than a userspace process.
- Obtain a radeon RV740/Juniper VBIOS image (e.g. dump with
nvramcui/ atombios tools, or the card's PCI expansion ROM). - Patch the
ATOM_FirmwareInfodata table: zerousReferenceClock(the field read atradeon_atombios.c:1193) β this is the simplest single-byte-field trigger and fires on the FIRSTpopulate_sclk_valuecall at driver init, before any user interaction.
Alternatively, locate the ASIC_InternalSS_Info table and set an entry's
usSpreadRateInKhz to 0 (or to a value > reference_clock*5/reference_divider
so the derived clk_s becomes 0) to hit the SS-path panics at
rv740_dpm.c:165-166/258-260; this path is gated on
ATOM_BIOS_INFO_ENGINE_CLOCK_SS_SUPPORT / _MEMORY_CLOCK_SS_SUPPORT
firmware flags (radeon_atombios.c:1519,1523) being set, which they
normally are on production BIOSes.
- Load the patched VBIOS: either reflash the card's SPI ROM, or in a
KVM/QEMU VFIO-passthrough setup attach a custom romfile
(
-device vfio-pci,...,romfile=evil.rom). - Boot DragonFlyBSD with the card present.
Expected result on the target console: kernel #DE trap / panic during
radeon attach (for the reference_clock==0 variant) or during the first DPM
power-state transition (for the SS variants); dmesg stops with a backtrace
rooted in rv740_populate_sclk_value / rv740_populate_mclk_value β do_div.
Success criterion: deterministic panic reproducing on every boot; no user credentials involved, purely a denial of service.
No escalation chain is possible from this bug class β it is a straight fault.
Recommended fix
Guard every division whose divisor is VBIOS-derived before executing it,
returning -EINVAL (matching the existing decoded_ref guard at :256) so
the caller's error-handling path skips the malformed state instead of
faulting.
--- a/sys/dev/drm/radeon/rv740_dpm.c
+++ b/sys/dev/drm/radeon/rv740_dpm.c
@@ -142,6 +142,9 @@ int rv740_populate_sclk_value(struct radeon_device *rdev, u32 engine_clock,
reference_divider = 1 + dividers.ref_div;
+ if (!reference_clock)
+ return -EINVAL;
+
tmp = (u64) engine_clock * reference_divider * dividers.post_div * 16384;
do_div(tmp, reference_clock);
fbdiv = (u32) tmp;
@@ -163,8 +166,13 @@ int rv740_populate_sclk_value(struct radeon_device *rdev, u32 engine_clock,
if (radeon_atombios_get_asic_ss_info(rdev, &ss,
ASIC_INTERNAL_ENGINE_SS, vco_freq)) {
u32 clk_s = reference_clock * 5 / (reference_divider * ss.rate);
- u32 clk_v = 4 * ss.percentage * fbdiv / (clk_s * 10000);
+ u32 clk_v;
+ if (!ss.rate || !clk_s)
+ return -EINVAL;
+ clk_v = 4 * ss.percentage * fbdiv / (clk_s * 10000);
cg_spll_spread_spectrum &= ~CLK_S_MASK;
cg_spll_spread_spectrum |= CLK_S(clk_s);
cg_spll_spread_spectrum |= SSEN;
@@ -254,11 +262,16 @@ int rv740_populate_mclk_value(struct radeon_device *rdev,
u32 decoded_ref = rv740_get_decoded_reference_divider(dividers.ref_div);
u32 clk_s, clk_v;
- if (!decoded_ref)
+ if (!decoded_ref || !ss.rate)
return -EINVAL;
clk_s = reference_clock * 5 / (decoded_ref * ss.rate);
+ if (!clk_s)
+ return -EINVAL;
clk_v = 0x40000 * ss.percentage *
(dividers.whole_fb_div + (dividers.frac_fb_div / 8)) / (clk_s * 10000);
+
mpll_ss1 &= ~CLKV_MASK;
mpll_ss1 |= CLKV(clk_v);
The same zero-validation should ideally be back-stopped at the source in
radeon_atombios.c where reference_freq (:1192-1193, :1216-1220) and
ss.rate (:1546) are read from the firmware, so every consumer is protected
rather than only rv740; but the per-call-site guards above are sufficient to
close the panic in this file.
References
sys/dev/drm/radeon/rv740_dpm.c:145,165-166,258-260β unguarded divisionssys/dev/drm/radeon/radeon_atombios.c:1192-1193βreference_freqparsed with no zero-checksys/dev/drm/radeon/radeon_atombios.c:1546βss.rateparsed with no zero-checksys/dev/drm/radeon/rv740_dpm.c:256-257β the existingdecoded_ref==0guard (proves author knew divisors can be hostile)sys/dev/drm/include/linux/math64.h:36-42βdo_divexpansion to runtimedivinstruction
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1992 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | Source verification narrative | 1.1 KB | β raw |
| fix.diff | suggested-fix | Fix: Add if(reference_clock==0) return -EINVAL before do_div. | 392 B | view raw |
| build.sh | build-script | Build/validation instructions | 366 B | view raw |
| run.sh | run-script | Run instructions (HW-gated, source-only) | 184 B | view raw |
| env.txt | environment | Guest environment | 404 B | view raw |
DF-1992 - Source Verification
Verdict: REPRODUCED (source-only confirmation)
Finding: sys/dev/drm/radeon/rv740_dpm.c:145
Mechanism: do_div(tmp, reference_clock) divides by reference_clock from VBIOS with no zero-check. Zero reference_clock β #DE trap panic.
Hardware dependency: Requires radeon RV740 GPU with malicious VBIOS.
Fix: Add if(reference_clock==0) return -EINVAL before do_div.
Verification method
Source-only confirmation. The cited code path was traced line-by-line in the audited sys/ tree. The bug exists exactly as described. This is a HW-gated driver finding β the vulnerable code path requires specific hardware (GPU, controller, PHY, TPM, etc.) not present in the QEMU audit guest. Runtime reproduction on this guest is not possible without the hardware.
Fix validation
fix.diff authored and applied to guest source. All 40 fixes in this batch
compile cleanly in a single combined kernel build: make -j6 nativekernel
KERNCONF=X86_64_GENERIC β rc=0, zero -Werror violations.
Kernel: DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026
Fix verification
not_testablenot_testable: HW-gated. fix.diff applies + compiles in batch build (rc=0 -Werror). Source trace confirms fix closes the path.
Batch build: 40 fix.diffs applied, make nativekernel β rc=0 -Werror. Bug at sys/dev/drm/radeon/rv740_dpm.c:145 source-confirmed.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- r
- v
- 7
- 4
- 0
- _
- d
- p
- m
- .
- c
- :
- 1
- 4
- 5
Detail
Exploit chain
none
Evidence (decisive lines)
Source trace sys/dev/drm/radeon/rv740_dpm.c:145. HW-gated (no HW in QEMU). Fix compiles in batch build rc=0.
PoC changes
Evidence pack: VERDICT.md, fix.diff, manifest.json. Fix: do_div by VBIOS reference_clock, no zero-check. Add zero guard.
Verified recommended fix
See fix.diff. do_div by VBIOS reference_clock, no zero-check. Add zero guard.
Verdict
REPRODUCED (source-only). sys/dev/drm/radeon/rv740_dpm.c:145: do_div by VBIOS reference_clock, no zero-check. Add zero guard.
No comments yet.