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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3613 Β· 12 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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

fixed
baseline reproduced→ patch + rebuild →patched clean

git 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.
↓ fix.diffper-fix-DF-3613

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.