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)
PoC verification
Evidence pack
findings/poc/DF-4128 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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
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
- sys/dev/drm/radeon/rv6xx_dpm.c:249
- sys/dev/drm/radeon/rv6xx_dpm.c:302
- sys/dev/drm/radeon/rv6xx_dpm.c:139
- sys/dev/drm/radeon/rv6xx_dpm.c:225
- sys/dev/drm/radeon/r600_dpm.c:446
- sys/dev/drm/radeon/r600d.h:1362
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_testablekernel-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
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.
No comments yet.