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

Spread-spectrum depth clk_v computed with wrapping u32 numerators β€” silent wrong SS programming (sclk twin of DF-4858's terminal effect, arithmetic class)

Summary

Both clk_v numerators (4*percentage*fbdiv and 0x40000*percentage*fb) wrap u32 for any real SS percentage (>~0.6%/0.8%) - the wrapped quotient is OR-ed into the 26-bit field with SSEN armed, so SS-enabled boards silently run wrong (replica shows ZERO) depth. Replicated verbatim on guest (exit 98 baseline vs exact depths fixed); u64 fix compile-validated on radeon.ko -Werror. DF-4858's wrong-variable store shape ABSENT here (rv740 programs both words correctly).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4931 Β· 10 files
FileTypeDescriptionSize
rv740_ssv.c β€” 8.0 KB view raw
build.sh β€” 109 B view raw
run.sh β€” 117 B view raw
build.log β€” 36 B view raw
run.log β€” 3.5 KB view raw
radeon_fix_apply.log β€” 719 B view raw
radeon_module_build.log β€” 2.7 KB view raw
fix.diff β€” 1.2 KB view raw
env.txt β€” 965 B view raw
VERDICT.md β€” 4.3 KB ↓ raw
VERDICT.md
↓ download raw

DF-4931 VERDICT

Status: reproduced (logic level, verbatim userland replica on the audit guest); kernel trigger hardware-gated (no RV740 GPU in the guest β€” 0 PCI 0x1002 devices), per the DF-4857/DF-4858 convention for this driver family.

The defect

rv740_populate_sclk_value and rv740_populate_mclk_value compute the spread-spectrum depth clk_v with 32-bit unsigned numerators whose operands are VBIOS-derived:

  • sclk β€” sys/dev/drm/radeon/rv740_dpm.c:166 u32 clk_v = 4 * ss.percentage * fbdiv / (clk_s * 10000); ss.percentage is u16 from the VBIOS ASIC_InternalSS_Info table (radeon_atombios.c:1546 area); fbdiv is the engine PLL feedback divisor computed at :144-146 and is inherently large (~1.8e7 for a 740 MHz engine clock β€” the accepted DF-4857 pack's "ok-mode" control measured fbdiv=18204444 on the sibling rv730 path).
  • mclk β€” sys/dev/drm/radeon/rv740_dpm.c:259-260 u32 clk_v = 0x40000 * ss.percentage * (whole_fb_div + (frac_fb_div / 8)) / (clk_s * 10000); whole_fb_div/frac_fb_div come from the AtomBIOS memory-PLL computation (:205-208).

C evaluates the full numerator product (left-to-right, both operands u32) before dividing, so any product > 2^32-1 wraps. Thresholds with ordinary values:

  • sclk: wraps whenever percentage * fbdiv > 2^30; with fbdiv β‰ˆ 3.6e6 that is percentage > 59, i.e. any engine SS deeper than ~0.6% (in 0.01% units).
  • mclk: wraps whenever percentage * whole_fb_div > 16384; with fb β‰ˆ 200 that is percentage > 82 (~0.8%).

The wrapped quotient then lands in the 26-bit CLK_V field of CG_SPLL_SPREAD_SPECTRUM_2 (:172-173, mask 0x3ffffff β€” rv740d.h:108) and the 26-bit CLKV field of MPLL_SS1 (:262-263, rv740d.h:112), while the correct value also fits the field β€” so the wrap is completely silent: SSEN gets armed (:170) with a wrong (often near-zero) depth on every SS-enabled board whose VBIOS SS percentages cross the (very low) threshold.

This is a sibling of the accepted DF-4858 (rv730 wrong-variable store β€” different mechanism, same terminal effect: SS enabled with wrong/zero depth) and is arithmetic-adjacent to DF-1992 (div-by-zero in the same blocks β€” different defect class: this is CWE-190 wrap, not CWE-369; DF-1992 is NOT re-reported here). The rv740 stores themselves are correct (rv740_dpm.c:262-266 programs CLKV into mpll_ss1 and CLKS into mpll_ss2 β€” the DF-4858 typo shape does NOT exist in rv740).

Reproduction (run.log, audit guest, cc 8.3)

Baseline = kernel arithmetic verbatim:

case numerator exact u32 wrapped programmed clk_v correct clk_v
S1 sclk 6% SS 8,621,623,200 31,688,608 0 (SSEN, zero depth) 191
S2 sclk 30% SS 43,108,116,000 158,443,040 3 957
M1 mclk 2% SS 10,485,760,000 1,895,825,408 227 1258
M2 mclk 5% SS 26,214,400,000 444,596,224 53 3146

All four exact values fit the 26-bit field β†’ silent misprogramming; exit 98. Fixed build (-DFIX_DF4931): every programmed depth equals the 64-bit exact value; exit 0.

Impact ceiling

Wrong SMC payload contents for GPU firmware only: spread spectrum armed with a wrong depth (engine and/or memory clock). Fixed-size register words, no OOB, no kernel memory corruption, no pointer involvement, no escalation chain. Realistic worst case is mclk/sclk instability or a GPU wedge (functional DoS of the graphics stack) on SS-enabled RV740-family boards. Same threat position as DF-1992 (VBIOS-supplied values; no unprivileged write path β€” DPM tables build at init / root-driven power-state transitions).

Fix validation

fix.diff (64-bit numerators, u64 clk_v/(u64) casts, field-masked stores): applied to the guest's /usr/src (patched rv740_dpm.c md5 3892509a4dee23072ec4bd42e5a68b72, pristine 98e600ce…), radeon.ko rebuilt under -Werror β€” compile+link OK (radeon_module_build.log, rv740_dpm.o in the link line). Harness fixed mode matches exact math. Guest source then reverted (md5 re-verified pristine). No .orig leftovers.

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 (64-bit numerators + masked stores) applied to guest /usr/src; radeon.ko rebuilt under -Werror with compile+link OK (radeon_fix_apply.log / radeon_module_build.log, rv740_dpm.o in link line); harness baseline exit 98 (4 wrapped depths) vs harness_fixed exit 0 with exact depths; guest source reverted (md5 re-verified pristine 98e600ce6e9b55011b16e2fe5a0302d8).

fix.diff; radeon_fix_apply.log; radeon_module_build.log; run.log (fixed sections); VERDICT.md
↓ fix.diffper-fix-DF-4931

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (decisive: baseline 'numerator 4*pct*fbdiv: exact=8621623200 u32-wrapped=31688608 << WRAPS u32', '[BUG] SSEN programmed with wrong depth: 0 instead of 191', exit=98; fixed 'RESULT: all programmed depths match the exact 64-bit math', exit=0); rv740_ssv.c (verbatim replica); radeon_fix_apply.log + radeon_module_build.log (patched build under -Werror); VERDICT.md

PoC changes

Authored fresh (no seed). Two host-compiler absence forced guest builds; one iteration each on the two dce110 harnesses (forward declarations, unbuffered stdout for pre-crash evidence); the rv740 replica built clean on the first guest attempt.

Verified recommended fix

Compute clk_v with 64-bit numerators before the field store: u64 clk_v = 4ULLss.percentagefbdiv/((u64)clk_s10000) at rv740_dpm.c:166 and (u64)0x40000ULLss.percentage(whole_fb_div+frac_fb_div/8)/((u64)clk_s10000) at :259-260, masking the stores with CLK_V_MASK/CLKV_MASK.

Verdict

Reproduced at logic level with a verbatim userland replica of the two rv740_dpm.c spread-spectrum blocks (sclk :159-175 with clk_v at :166, mclk :246-268 with clk_v at :259-260; masks/shifts from rv740d.h:100-116) run on the audit guest (cc 8.3); the kernel-level trigger is hardware-gated (no RV740 GPU β€” 0 PCI 0x1002 devices), per the DF-4857/DF-4858 convention for this family. Both clk_v numerators (4ss.percentagefbdiv; 0x40000ss.percentage(whole_fb_div+frac/8)) are evaluated in 32-bit unsigned arithmetic over VBIOS-derived operands and wrap for entirely plausible values β€” sclk wraps for any SS percentage > ~0.6% (fbdiv~3.6e6), mclk for > ~0.8% at fb~200. The wrapped quotient is OR-ed into the 26-bit CLK_V (CG_SPLL_SPREAD_SPECTRUM_2) / CLKV (MPLL_SS1) SMC fields while SSEN is armed β€” the exact value also fits the field, so the misprogramming is silent: replica shows SSEN with depth 0 (expected 191) and CLKV=227 (expected 1258). Distinct from DF-1992 (CWE-369 zero divisors at :145/:165-166/:258-260 β€” not re-reported) and from DF-4858 (rv730 wrong-variable store β€” the rv740 stores at :262-266 are correct). Impact ceiling is wrong GPU firmware values (clock spread instability / GPU wedge): fixed-size register words, no OOB, no kernel memory corruption, no escalation chain. Fix validated: fix.diff (u64 numerators + field-masked stores) applied to guest /usr/src (patched md5 3892509a), radeon.ko rebuilt under -Werror (link OK, radeon_module_build.log), harness_fixed matches exact 64-bit math on all scenarios; guest source reverted (md5 re-verified pristine).