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

r600_pm_init_profile hardcodes clock-mode indices without num_clock_modes validation β€” default-config OOB heap read of per-state clock_info

Summary

r600_pm_init_profile's non-R600 branch hardcodes clock-mode indices up to 2 - including dpms_on_cm_idx=2 for the DEFAULT profile - with no validation against the target power state's num_clock_modes. On r6xx discrete ASICs with PowerPlayInfo frev1/2 tables every state has exactly one clock mode and a 1-element clock_info allocation, so radeon_set_power_state reads clock_info[2].sclk/.mclk and r600_pm_misc reads .voltage two elements past the allocation; the garbage values are programmed as engine/memory clocks and VOLTAGE_SW voltage. Happens automatically - PM_PROFILE_DEFAULT and PM_METHOD_PROFILE are the out-of-the-box settings, invoked on every modeset with a display connected; no sysfs or DYNPM needed, unlike DF-1196/DF-3734. Impact: OOB kernel heap read feeding GPU clock/voltage programming - local DoS / out-of-spec hardware operation; no direct kernel-memory disclosure to userspace. Fix: clamp at the consumer choke point in radeon_set_power_state (covers hardcoded indices and the DYNPM increment).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3735 Β· 7 files
FileTypeDescriptionSize
README.md β€” 2.6 KB ↓ raw
VERDICT.md β€” 5.3 KB ↓ raw
run.sh β€” 129 B view raw
run.log β€” 227 B view raw
env.txt β€” 231 B view raw
fix.diff β€” 1.8 KB view raw
verdict.json β€” 3.9 KB view raw

DF-3735 β€” r600_pm_init_profile hardcodes clock-mode indices without validating num_clock_modes β€” OOB heap read in the default PM profile

File: sys/dev/drm/radeon/r600.c:680-763 (consumer sites r600.c:771, radeon_pm.c:181-199) Class: kernel OOB heap read β€” CWE-125 / CWE-1284 Severity: Medium Β· Confidence: likely Bucket: kernleak

Build / Run / Expected

Kernel-internal PM path β€” no userspace trigger exists without a physical r6xx discrete GPU carrying a PowerPlayInfo frev1/2 BIOS (single clock mode per state). The audit guest is virtio-only and its kernel build ships no radeonkms.ko (env.txt). Verification is source-level (VERDICT.md) plus the guest presence check:

dfbsd-qemu/vm.sh run_root 'ls /boot/kernel/radeonkms.ko; pciconf -l | grep 1002'

Expected on real hardware (e.g. RV630/RV635/RV670, frev1/2 PowerPlay, β‰₯2 power states, display connected): on the first radeon_pm_compute_clocks() after a modeset, the DEFAULT profile yields requested_clock_mode_index = 2 while the selected power state's clock_info allocation holds exactly one element β†’ power_state[ps].clock_info[2].sclk/.mclk and .voltage are read two elements past the allocation; garbage values are programmed as GPU engine clock, memory clock and (VOLTAGE_SW) voltage.

Trigger conditions

  1. Non-IGP, non-R600 r6xx/r7xx discrete ASIC (r600_pm_init_profile else branch, r600.c:679) β€” wired to RV610..RV770-family asic blocks (radeon_asic.c:987,1073,1272,1392).
  2. num_power_states < 4 (r600.c:680) and β‰₯ 2 (radeon_pm.c:1735 guard).
  3. PowerPlayInfo frev 1/2 BIOS β†’ every state: num_clock_modes == 1, 1-element clock_info allocation (radeon_atombios.c:2126-2133).
  4. active_crtc_count > 0 (display running) β†’ DEFAULT profile's dpms_on_cm_idx = 2 is applied (radeon_pm.c:138-143).

No sysfs interaction and no DYNPM needed β€” PM_PROFILE_DEFAULT is the out-of-the-box profile (radeon_pm.c:1359), and radeon_pm_compute_clocks_old calls radeon_pm_update_profile()+radeon_pm_set_clocks() for PM_METHOD_PROFILE (radeon_pm.c:1753-1756), which is the default method on DragonFly when DPM is off (radeon_pm.c:1584-1587).

Fix

fix.diff β€” clamp the profile-initialized clock-mode indices to each target power state's num_clock_modes - 1 at profile-init time. Never applied to sys/; authored against the read-only tree.

Artifacts

  • VERDICT.md β€” full source-level proof
  • run.sh, run.log, env.txt β€” guest module/hardware presence check
  • fix.diff β€” proposed fix
  • manifest.json, verdict.json
VERDICT.md
↓ download raw

VERDICT β€” DF-3735

status: inconclusive (runtime reproduction impossible on this audit guest: virtio-only, no AMD PCI device, no radeonkms.ko in the guest kernel build β€” env.txt/run.log). The defect is source-certain; trace below.

Root cause

r600_pm_init_profile(), non-R600 branch, hardcodes clock-mode indices up to 2 for the profiles table without validating the selected power state's num_clock_modes:

sys/dev/drm/radeon/r600.c:679-685
    } else {
        if (rdev->pm.num_power_states < 4) {
            /* default */
            rdev->pm.profiles[PM_PROFILE_DEFAULT_IDX].dpms_off_ps_idx = rdev->pm.default_power_state_index;
            rdev->pm.profiles[PM_PROFILE_DEFAULT_IDX].dpms_on_ps_idx = rdev->pm.default_power_state_index;
            rdev->pm.profiles[PM_PROFILE_DEFAULT_IDX].dpms_off_cm_idx = 0;
            rdev->pm.profiles[PM_PROFILE_DEFAULT_IDX].dpms_on_cm_idx = 2;      // <-- hardcoded 2

(further hardcoded cm_idx of 1 and 2 at r600.c:695, 700, 710, 715, 721, 735, 741, 750, 755, 761 for LOW/MID/HIGH SH/MH profiles.)

The indices are consumed unconditionally in the default configuration:

sys/dev/drm/radeon/radeon_pm.c:133-143 (radeon_pm_update_profile)
    } else {
        rdev->pm.requested_power_state_index =
            rdev->pm.profiles[rdev->pm.profile_index].dpms_on_ps_idx;
        rdev->pm.requested_clock_mode_index =
            rdev->pm.profiles[rdev->pm.profile_index].dpms_on_cm_idx;   // = 2
    }

pm.profile = PM_PROFILE_DEFAULT out of the box (radeon_pm.c:1359, radeon_pm_init_old), pm_method = PM_METHOD_PROFILE is the default on DragonFly when DPM is off (radeon_pm.c:1584-1587), and radeon_pm_compute_clocks_old runs update_profile()+set_clocks() for the profile method on every modeset/DPMS change (radeon_pm.c:1753-1756, guard num_power_states >= 2 at radeon_pm.c:1735).

Why index 2 is out of bounds on real r6xx hardware

power_state[ps].clock_info is a per-state heap pointer (radeon.h:1300-1311). For PowerPlayInfo frev 1/2 tables (r6xx-era BIOSes β€” the exact ASICs this file's discrete branch covers: RV610/RV630/RV635/RV670/ RV620), the parser allocates exactly one element per state and sets num_clock_modes = 1:

sys/dev/drm/radeon/radeon_atombios.c:2126-2133
    if (!rdev->pm.power_state[state_index].clock_info) {
        rdev->pm.power_state[state_index].clock_info =
            kzalloc(sizeof(struct radeon_pm_clock_info), GFP_KERNEL);
    }
    ...
    rdev->pm.power_state[state_index].num_clock_modes = 1;

So on the first radeon_pm_set_clocks() with an active display:

sys/dev/drm/radeon/radeon_pm.c:180-182 (radeon_set_power_state)
    if (radeon_gui_idle(rdev)) {
        sclk = rdev->pm.power_state[rdev->pm.requested_power_state_index].
            clock_info[rdev->pm.requested_clock_mode_index].sclk;   // clock_info[2] on a
                                                                       // 1-element alloc

The early-return at radeon_pm.c:176-178 does not save it: current_clock_mode_index starts at 0 (kzalloc'd rdev) β‰  2, so the read executes.

OOB consumers and impact

  1. radeon_pm.c:181-182 .sclk, radeon_pm.c:198-199 .mclk β€” OOB heap reads; values clamped only upward (> default_sclk/default_mclk), then programmed via radeon_set_engine_clock() / radeon_set_memory_clock() (radeon_pm.c:222-237) β†’ out-of-spec/garbage GPU clocks from adjacent slab contents.
  2. r600_pm_misc() r600.c:766-783 β€” ps->clock_info[req_cm_idx].voltage OOB; garbage VOLTAGE_SW voltage β†’ radeon_atom_set_voltage() (r600.c:777-778) β†’ arbitrary voltage selector programmed.

Impact ceiling: default-configuration OOB kernel heap read (kmalloc slab adjacent data) whose contents drive GPU clock/voltage programming β€” local DoS / hardware misbehavior; no direct kernel-memory disclosure to userspace (read values are written to GPU registers, not copied out). Medium severity, config-dependent (needs frev1/2 BIOS hardware in the supported r6xx fleet).

Negative checks performed

  • The CHIP_R600 branch (r600.c:642-678) uses only cm 0 β€” safe.
  • IGP RS780/RS880 use rs780_pm_init_profile (radeon_asic.c:1166) which uses only cm 0 β€” safe.
  • pplib (frev β‰₯ 4 / parser 2) BIOSes allocate multi-mode clock_info arrays (radeon_atombios.c:2608, 2704) β€” not vulnerable; the exposure is specific to the frev1/2 parser path.
  • COMBIOS r6xx is impossible (this file requires ATOMBIOS, r600.c:3272-3276).

Guest verification performed

env.txt/run.log: DragonFly 6.5-DEVELOPMENT x86_64; no radeonkms.ko in /boot/kernel (224 radeonkmsfw_*.ko firmware stubs only, including R600/RV61x/RV63x/RV670 firmware β€” the stack targets this hardware family); pciconf -l shows no AMD device. Runtime trigger impossible on this guest; no kernel fix build attempted for the same reason (fix_status: not_testable).

Clamp every hardcoded profile *_cm_idx to the target power state's mode count at profile-init time β€” see fix.diff (guards on power_state[ps].num_clock_modes before assigning 1/2).

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

fix.diff authored against the read-only sys/ tree; kernel fix build/validation not attempted β€” the guest has no radeon hardware and no radeonkms.ko, so neither baseline nor patched behavior is observable.

findings/poc/DF-3735/fix.diff
↓ fix.diffper-fix-DF-3735

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (full source-level trace with path:line); fix.diff (consumer-side clamp in radeon_set_power_state); run.log + env.txt (guest: no AMD GPU, no radeonkms.ko β€” 224 radeonkmsfw_* firmware stubs only, including R600/RV61x/RV63x/RV670 images); decisive lines: r600.c:685 (hardcoded dpms_on_cm_idx=2), radeon_pm.c:133-143 + 1753-1756 + 181-199 (default-config consumption), radeon.h:1300-1311 (clock_info per-state heap pointer), radeon_atombios.c:2126-2133 (1-element allocation, num_clock_modes=1).

PoC changes

No runnable PoC exists for a kernel-internal PM index bug without r6xx hardware; replaced with a source-level proof plus guest module/hardware presence check.

Verified recommended fix

Clamp requested_clock_mode_index to power_state[requested_power_state_index].num_clock_modes-1 in radeon_set_power_state before first use (covers the hardcoded profile indices and the DYNPM increment) β€” see fix.diff.

Verdict

Source-certain OOB heap read reachable in the DEFAULT power-management configuration: r600_pm_init_profile's non-R600 branch (r600.c:680-715) hardcodes clock-mode indices up to 2 (dpms_on_cm_idx=2 for PM_PROFILE_DEFAULT_IDX at r600.c:685) with no validation against the target power state's num_clock_modes. On r6xx discrete ASICs (RV610..RV670, wired at radeon_asic.c:987/1073/1272/1392) with PowerPlayInfo frev1/2 BIOS tables every state has exactly one clock mode and a 1-element clock_info allocation (radeon_atombios.c:2126-2133), so radeon_set_power_state reads power_state[ps].clock_info[2].sclk/.mclk (radeon_pm.c:181-199) and r600_pm_misc reads .voltage (r600.c:771) two elements past the allocation on the first set_clocks with an active display; the garbage values are programmed as engine/memory clocks and VOLTAGE_SW voltage. No sysfs or DYNPM needed β€” PM_PROFILE_DEFAULT + PM_METHOD_PROFILE are the out-of-the-box settings (radeon_pm.c:1359, 1584-1587), invoked from radeon_pm_compute_clocks_old at radeon_pm.c:1753-1756. Runtime reproduction was impossible on the audit guest: virtio-only (no AMD PCI device) and radeonkms.ko is not built for the guest kernel (env.txt/run.log).