Integer divide-by-zero on zero PLL reference clock from VBIOS FirmwareInfo (do_div at si_calculate_sclk_params:4811; mpll divide at si_populate_mclk_value:4927)
Summary
radeon_atom_get_clock_info stores usCoreReferenceClock/usMemoryReferenceClock into spll/mpll.reference_freq with no zero fallback (unlike pll_out_min, which gets defaults). si_calculate_sclk_params:4811 does do_div(tmp, reference_clock) for every performance level of every power state and 256x from si_init_smc_spll_table; si_populate_mclk_value:4927 divides freq_nom by the mpll reference. reference_freq==0 -> #DE -> panic on the first level upload at si_dpm_enable. VERIFIED: harness executes the exact 64-bit division with reference_freq==0 in forked children - SIGFPE at both replicas; fixed-path ref=0 -> rc=-1 no trap, ref=27000 -> rc=0 fbdiv unchanged. Deterministic boot-time kernel panic on SI radeon hardware from a crafted VBIOS.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3613 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | β | 4.3 KB | view raw | |
| build.sh | β | 66 B | view raw | |
| run.sh | β | 90 B | view raw | |
| build.log | β | 11 B | view raw | |
| run.log | β | 823 B | view raw | |
| fix.diff | β | 497 B | view raw | |
| fix_build.log | β | 1.6 KB | view raw | |
| fix_run.log | β | 189 B | view raw | |
| VERDICT.md | β | 2.7 KB | β raw | |
| README.md | β | 1.6 KB | β raw | |
| manifest.json | β | 767 B | view raw | |
| verdict.json | β | 3.0 KB | view raw |
DF-3613 β Integer divide-by-zero on zero PLL reference clock from VBIOS FirmwareInfo (si_dpm.c:4811 do_div, :4927 mpll divide)
Severity: Medium Confidence: certain Status: reproduced (harness) Impact: panic
What
radeon_atom_get_clock_info stores usCoreReferenceClock/usMemoryReferenceClock into spll/mpll.reference_freq with no zero fallback (pll_out_min gets a 64800/20000 default at radeon_atombios.c:1202, the reference clocks do not). si_calculate_sclk_params:4811 does do_div(tmp, reference_clock) for EVERY performance level of every power state (and 256x from si_init_smc_spll_table:2866) during si_dpm_enable; si_populate_mclk_value:4927 divides freq_nom by the mpll reference. reference_freq==0 -> #DE -> panic on the first level upload. Harness executes the exact 64-bit division in a forked child: SIGFPE == kernel #DE panic.
Reproduce
sh build.sh # cc -O2 -Wall -o harness harness.c sh run.sh # prints [A] buggy-path behaviour, [B] fixed-path behaviour
Expected decisive output (see run.log): the [A] section shows the fault or
corruption the kernel would take; the [B] section shows the behaviour with
fix.diff applied.
No AMD GPU is attached to the audit QEMU guest (virtio-only), so the radeon DPM path cannot be exercised live; the harness replicates the exact kernel expressions with crafted-VBIOS inputs (house methodology, cf. DF-1137..1140).
Fix
fix.diff β validated: git apply --check clean; make si_dpm.o in
sys/dev/drm/radeon with KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC
compiles -Werror clean with all five pass-2 fixes stacked; harness [B]
shows the primitive gone.
DF-3613 β Verdict
Verdict: REPRODUCED (source-level + harness) β integer divide-by-zero (#DE) panic from zero PLL reference clock
Distinct div0 sink from DF-1137 (fan) and DF-3612 (SS rate): the divisor is the FirmwareInfo PLL reference clock.
Bug confirmation
radeon_atombios.c:1188-1193 /* system clock */ [SI is DCE6 -> DCE4 branch]
spll->reference_freq = le16(firmware_info->info_21.usCoreReferenceClock); // no fallback
radeon_atombios.c:1215-1220 /* memory clock */
mpll->reference_freq = le16(firmware_info->info_21.usMemoryReferenceClock); // no fallback
Contrast pll_out_min, which DOES get a default when 0
(radeon_atombios.c:1173-1178, 1202-1207, 1229-1234) β the reference clocks
were simply forgotten. si_dpm.c divides by them unconditionally:
si_dpm.c:4798 u32 reference_clock = rdev->clock.spll.reference_freq;
si_dpm.c:4810 tmp = (u64)engine_clock * reference_divider * dividers.post_div * 16384;
si_dpm.c:4811 do_div(tmp, reference_clock); // ==0 -> #DE
si_dpm.c:4920 u32 reference_clock = rdev->clock.mpll.reference_freq;
si_dpm.c:4927 tmp = freq_nom / reference_clock; // ==0 -> #DE
si_calculate_sclk_params is called for every level of every power state
(si_populate_sclk_value) and 256 times from si_init_smc_spll_table
(si_dpm.c:2866) β i.e. always, on the first sclk programming of
si_dpm_enable.
Harness confirmation
run.log:
[A1] si_dpm.c:4811 do_div(tmp, spll.reference_freq==0)
child died from SIGFPE (signal 4): 64-bit divide by zero
[A2] si_dpm.c:4927 freq_nom / (mpll.reference_freq==0)
child died from SIGFPE (signal 4): 64-bit divide by zero
[B] ref=0 -> rc=-1 (level upload refused, no trap)
ref=27000 -> rc=0, fbdiv=114081 (normal programming)
Trigger conditions (not met on this guest)
SI GPU + radeon DPM (virtio-only guest) + FirmwareInfo with usCoreReferenceClock == 0 (or usMemoryReferenceClock == 0 for the mclk sink).
Exploit chain
None β #DE; boot-time DoS panic.
Fix
fix.diff β if (reference_clock == 0) return -EINVAL; at the top of both
consumers (si_calculate_sclk_params, si_populate_mclk_value). Validation:
apply-check OK; make si_dpm.o -Werror clean (stacked); harness [B]
refuses the zero-clock case while the sane-clock arithmetic is unchanged.
fix_status: fixed (compile + harness-level).
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
fixedgit apply --check OK (standalone and stacked with the other four si_dpm.c fixes); targeted module TU compile make KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC si_dpm.o => rc=0, -Werror clean (87080 B). Harness fixed-path shows the primitive gone (fix.diff adds if (reference_clock == 0) return -EINVAL; in si_calculate_sclk_params and si_populate_mclk_value. git apply --check OK; stacked module TU compile -Werror clean; harness [B] no trap, sane-clock path unchanged.)
fix.diff adds `if (reference_clock == 0) return -EINVAL;` in si_calculate_sclk_params and si_populate_mclk_value. git apply --check OK; stacked module TU compile -Werror clean; harness [B] no trap, sane-clock path unchanged.
Confirmed kernel references
Detail
Exploit chain
crafted VBIOS FirmwareInfo with usCoreReferenceClock (or usMemoryReferenceClock) == 0 -> first si_populate_sclk_value at DPM enable -> do_div by 0 -> #DE -> panic (boot-time DoS)
Evidence (decisive lines)
run.log [A1]: 'child died from SIGFPE (signal 4): 64-bit divide by zero' at :4811 replica; [A2] same at :4927; [B]: 'ref=0 -> rc=-1 (level upload refused, no trap)'; 'ref=27000 -> rc=0, fbdiv=114081'
PoC changes
n/a (pass-2 finding authored with its harness; no seeded PoC to fix)
Verified recommended fix
see fix.diff
Verdict
Distinct div0 sink from DF-1137/DF-3612. radeon_atom_get_clock_info stores usCoreReferenceClock/usMemoryReferenceClock into spll/mpll.reference_freq with no zero fallback (pll_out_min gets a 64800/20000 default at radeon_atombios.c:1202, the reference clocks do not). si_calculate_sclk_params:4811 does do_div(tmp, reference_clock) for EVERY performance level of every power state (and 256x from si_init_smc_spll_table:2866) during si_dpm_enable; si_populate_mclk_value:4927 divides freq_nom by the mpll reference. reference_freq==0 -> #DE -> panic on the first level upload. Harness executes the exact 64-bit division in a forked child: SIGFPE == kernel #DE panic.
No comments yet.