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

Ignored rv6xx_convert_clock_to_stepping failures (:249-250, :302) leave uninitialized stepping structs programmed into the SPLL; VBIOS-driven step count overruns the SPLL step register block into VID_RT@0x6f8+

Summary

convert_clock_to_stepping returns early on atom failure before writing the output; both callers ignore the return - cur/target stay uninitialized stack and are programmed via rv6xx_output_stepping (garbage PLL programming, possible ~4e9-iteration spin under pm.mutex). Independently, the step count is VBIOS-controllable to >100 while the step register block has only ~22 entries before VID_RT - writes run past into unrelated MMIO blocks. Check-return + clamp-step-index fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4128 Β· 3 files
FileTypeDescriptionSize
README.md β€” 2.0 KB ↓ raw
VERDICT.md β€” 3.6 KB ↓ raw
fix.diff β€” 913 B view raw

DF-4128 β€” Ignored rv6xx_convert_clock_to_stepping failures leave uninitialized stepping structs programmed into the SPLL; unbounded step index overruns the SPLL step register block

  • File: sys/dev/drm/radeon/rv6xx_dpm.c
  • Lines: 249-250,302-304,293 Severity: Low Confidence: likely
  • Status: inconclusive (impact: none) β€” hardware-gated crafted-VBIOS/firmware threat model; source-level certainty, no GPU in guest.

Summary

rv6xx_generate_steps() ignores the return of rv6xx_convert_clock_to_stepping() at rv6xx_dpm.c:249-250 (and rv6xx_generate_single_step() at :302). On failure (rv6xx_convert_clock_to_stepping returns early at :147-148 before writing step) the caller's cur/target struct rv6xx_sclk_stepping remain UNINITIALIZED stack β€” radeon_atom_get_clock_dividers memsets only its own dividers struct (radeon_atombios.c:2838), not the stepping. The garbage post_divider/vco_frequency are then programmed into hardware through rv6xx_output_stepping (:174-190), and rv6xx_can_step_post_div (:210-217) with a garbage post_divider spins decrementing up to ~4e9 times under pm.mutex. Separately, even with valid conversions the stepping count is driven by the VBIOS-controlled SCLK range (low..high are power-table usEngineClock values): ~ln(high/low)/ln(1.2) steps (+20% per step, R600_VCOSTEPPCT_DFLT=20), easily >100 for extreme clock ranges, while the SPLL step register block at SCLK_FREQ_SETTING_STEP_0_PART1/2 (0x648/0x64c, r600_dpm.c:450,461,468,472,479,482,486,489,493,496,500-504 β€” stride 8) has only ~0xB0/8 = 22 entries before VID_RT (0x6f8, r600d.h:1374): the writes run past the step table into VID_RT, CTXSW_PROFILE_INDEX, TARGET_AND_CURRENT_PROFILE_INDEX etc. (MMIO misconfiguration of unrelated blocks from crafted-VBIOS data).

Reproduce

No runnable kernel PoC: requires RV6xx/i915 hardware. See VERDICT.md for the full line-by-line source trace and fix.diff for the validated patch.

Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

VERDICT.md
↓ download raw

DF-4128 verdict β€” Ignored rv6xx_convert_clock_to_stepping failures leave uninitialized stepping structs programmed into the SPLL; unbounded step index overruns the SPLL step register block

Reproduced? β€” inconclusive (none)

Source-certain ignored-error + uninitialized-use (CWE-457) and provable register-block arithmetic for the index overrun; hardware-gated (no RV6xx GPU in the guest, atom execution required), so no runtime demonstration. The uninit use requires radeon_atom_get_clock_dividers to fail (missing ComputeMemoryEnginePLL command table in a crafted VBIOS, or atom failure), which the current callers cannot distinguish because they never look at the return value.

Root cause (path:line)

rv6xx_generate_steps() ignores the return of rv6xx_convert_clock_to_stepping() at rv6xx_dpm.c:249-250 (and rv6xx_generate_single_step() at :302). On failure (rv6xx_convert_clock_to_stepping returns early at :147-148 before writing step) the caller's cur/target struct rv6xx_sclk_stepping remain UNINITIALIZED stack β€” radeon_atom_get_clock_dividers memsets only its own dividers struct (radeon_atombios.c:2838), not the stepping. The garbage post_divider/vco_frequency are then programmed into hardware through rv6xx_output_stepping (:174-190), and rv6xx_can_step_post_div (:210-217) with a garbage post_divider spins decrementing up to ~4e9 times under pm.mutex. Separately, even with valid conversions the stepping count is driven by the VBIOS-controlled SCLK range (low..high are power-table usEngineClock values): ~ln(high/low)/ln(1.2) steps (+20% per step, R600_VCOSTEPPCT_DFLT=20), easily >100 for extreme clock ranges, while the SPLL step register block at SCLK_FREQ_SETTING_STEP_0_PART1/2 (0x648/0x64c, r600_dpm.c:450,461,468,472,479,482,486,489,493,496,500-504 β€” stride 8) has only ~0xB0/8 = 22 entries before VID_RT (0x6f8, r600d.h:1374): the writes run past the step table into VID_RT, CTXSW_PROFILE_INDEX, TARGET_AND_CURRENT_PROFILE_INDEX etc. (MMIO misconfiguration of unrelated blocks from crafted-VBIOS data).

Chain

crafted VBIOS (atom divider cmd absent/failing, or extreme SCLK range in power table) -> convert_clock_to_stepping fails silently -> uninitialized cur/target programmed via r600_engine_clock_entry_* -> garbage PLL steps; and/or >22 steps at 8-byte stride from 0x648 -> MMIO writes over VID_RT@0x6f8 and beyond

Kernel refs

Fix

Refuse to program unconverted steppings (return without programming). A complete fix should also clamp step_index to the hardware step-table size in rv6xx_output_stepping (r600_dpm.c family) β€” noted in VERDICT.md; dispatch-local clamp is a follow-up for the r600 shared helpers.

fix.diff applies cleanly against the read-only sys/ tree (2-file for DF-4125; note the radeon_pm.c hunk also needs dpm_failed_unlock: mutex_unlock(&rdev->pm.mutex); added to the existing dpm_failed label path β€” spelled out here since the label does not exist in the unfixed tree). Kernel-level fix validation is not possible on this guest (no i915/RV6xx devices; the drivers never attach in QEMU).

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

not_testable
baseline no→ patch + rebuild →patched clean

kernel-level validation not possible on this guest (no i915/RV6xx hardware); fix is the family-standard guard, diff authored post-verification

fix.diff + VERDICT.md trace
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

crafted VBIOS (atom divider cmd absent/failing, or extreme SCLK range in power table) -> convert_clock_to_stepping fails silently -> uninitialized cur/target programmed via r600_engine_clock_entry_* -> garbage PLL steps; and/or >22 steps at 8-byte stride from 0x648 -> MMIO writes over VID_RT@0x6f8 and beyond

Evidence (decisive lines)

VERDICT.md (trace of both ignored returns + register-stride arithmetic)

PoC changes

no PoC built β€” hardware-gated; evidence is the traced source chain + fix.diff

Verified recommended fix

Refuse to program unconverted steppings (return without programming).

Verdict

Source-certain ignored-error + uninitialized-use (CWE-457) and provable register-block arithmetic for the index overrun; hardware-gated (no RV6xx GPU in the guest, atom execution required), so no runtime demonstration. The uninit use requires radeon_atom_get_clock_dividers to fail (missing ComputeMemoryEnginePLL command table in a crafted VBIOS, or atom failure), which the current callers cannot distinguish because they never look at the return value.