Unchecked requested_clock_mode_index++ in r600 DYNPM downclock (discrete branch) β one-past-end OOB heap read of per-state clock_info
Summary
r600_pm_get_dynpm_state's DYNPM_ACTION_DOWNCLOCK discrete-ASIC branch increments requested_clock_mode_index without any comparison to the selected power state's num_clock_modes. On r6xx discrete hardware with PowerPlayInfo frev1/2 BIOS tables every state has exactly one clock mode, so the increment yields index 1 == mode count and clock_info[1] is read past the end of a 1-element kzalloc (DRM_DEBUG_DRIVER arguments are evaluated even with debug printing disabled) and via r600_pm_misc / radeon_set_power_state, whose garbage sclk/mclk/voltage are then programmed to the GPU. Sibling of DF-1196 but a distinct defect: that is the power_state index++ in the IGP/R600 branch vs num_power_states; this is the clock_mode index++ in the discrete branch vs num_clock_modes - different variable, different array dimension, different bounds source. Local user on r6xx/r7xx discrete with dynpm: one-past-end heap read driving GPU clock/voltage programming. On DragonFly the DYNPM method is additionally unreachable today (PM sysfs attrs #if 0) - latent like DF-1196.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3734 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.3 KB | β raw | |
| VERDICT.md | β | 5.5 KB | β raw | |
| run.sh | β | 129 B | view raw | |
| run.log | β | 227 B | view raw | |
| env.txt | β | 231 B | view raw | |
| fix.diff | β | 908 B | view raw | |
| verdict.json | β | 3.8 KB | view raw |
DF-3734 β unchecked requested_clock_mode_index++ in r600 DYNPM downclock (discrete branch)
File: sys/dev/drm/radeon/r600.c:482-487
Class: kernel OOB heap read (one-past-end) β CWE-125 / CWE-1284
Severity: Low Β· Confidence: likely (source-certain defect; trigger gated on DYNPM)
Bucket: kernleak (sibling of DF-1196, distinct site β see "not a duplicate" below)
Build / Run / Expected
There is no runnable userspace trigger: the bug is an internal index arithmetic
defect on the kernel PM worker path and requires a physical r6xx/r7xx GPU with a
specific ATOM BIOS PowerPlay table, plus pm_method=dynpm. The guest used for
this audit is virtio-only (no AMD PCI device) and its kernel build ships no
radeonkms.ko β see env.txt. The verification performed is therefore
source-level (full trace in VERDICT.md) plus a guest module/hardware
presence check (re-run any time):
dfbsd-qemu/vm.sh run_root 'ls /boot/kernel/radeonkms.ko; pciconf -l | grep 1002'
Expected result on real hardware (RV610/RV630/RV635/RV670 etc. with
PowerPlayInfo frev1/2 BIOS, β₯2 power states, display connected, DYNPM enabled):
power_state[req_ps].clock_info[1] is read one element past a 1-element
kzalloc β at r600.c:516-522 (DRM_DEBUG_DRIVER args, evaluated even with
debug printing off) and via r600_pm_misc() at r600.c:771 /
radeon_pm.c:181-199 β garbage sclk/mclk/voltage programmed to the GPU.
Trigger conditions (all required)
pm_method == PM_METHOD_DYNPMβ on DragonFly this is currently unreachable: the only transitions to DYNPM are the sysfs storeradeon_set_pm_method(radeon_pm.c:444-449), and all sysfs attribute creation is compiled out (#if 0atradeon_pm.c:1603-1616and1638-1647). The defect is latent in this tree, live in upstream-style configs.- Non-IGP, non-R600 ASIC (RV610..RV770 discrete) β the
elsebranch ofr600_pm_get_dynpm_state(r600.c:442-514). DYNPM_ACTION_DOWNCLOCKwithrequested_clock_mode_index == 0(always true when the state'snum_clock_modes == 1).active_crtc_count > 0andclock_info[0].flags & RADEON_PM_MODE_NO_DISPLAY(set by default on non-default state 0 βradeon_atombios.c:2075-2078).- Selected power state built by the frev1/2 parser β every state has
num_clock_modes == 1and a 1-elementclock_infoallocation (radeon_atombios.c:2127-2133).
Then requested_clock_mode_index++ (r600.c:486) makes the index 1 == the
state's mode count β one-past-end.
Why this is NOT a re-report of DF-1196
DF-1196 covers the power_state index increment at r600.c:405 in the
IGP/R600 branch (requested_power_state_index++ vs num_power_states).
DF-3734 is the clock_mode index increment at r600.c:486 in the
discrete-ASIC branch (requested_clock_mode_index++ vs num_clock_modes) β
different variable, different array (clock_info is a per-state heap
allocation, power_state the outer array), different branch, different
bounds source.
Fix
fix.diff (bounds-check the increment). Never applied to sys/ β authored
against the read-only tree.
Artifacts
VERDICT.mdβ full source-level proof and reachability analysisrun.sh,run.log,env.txtβ guest module/hardware presence checkfix.diffβ proposed fixmanifest.json,verdict.json
VERDICT β DF-3734
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). The defect itself is source-certain; the trace below is the
evidence.
Root cause
r600_pm_get_dynpm_state(), discrete-ASIC branch (non-IGP, non-R600),
DYNPM_ACTION_DOWNCLOCK:
sys/dev/drm/radeon/r600.c:469-487
case DYNPM_ACTION_DOWNCLOCK:
if (rdev->pm.requested_power_state_index == rdev->pm.current_power_state_index) {
if (rdev->pm.current_clock_mode_index == 0) {
rdev->pm.requested_clock_mode_index = 0; // <-- can be 0
rdev->pm.dynpm_can_downclock = false;
} else
rdev->pm.requested_clock_mode_index =
rdev->pm.current_clock_mode_index - 1;
} else {
rdev->pm.requested_clock_mode_index = 0; // <-- can be 0
rdev->pm.dynpm_can_downclock = false;
}
if ((rdev->pm.active_crtc_count > 0) &&
(rdev->pm.power_state[rdev->pm.requested_power_state_index].
clock_info[rdev->pm.requested_clock_mode_index].flags &
RADEON_PM_MODE_NO_DISPLAY)) {
rdev->pm.requested_clock_mode_index++; // :486 UNCHECKED
}
The increment at r600.c:486 has no comparison against
power_state[requested_power_state_index].num_clock_modes.
Why the index can be out of bounds
struct radeon_power_state.clock_info is a per-state heap pointer
(radeon.h:1300-1311), sized by the BIOS table parser. For PowerPlayInfo
frev 1/2 (r6xx-era BIOSes) the parser gives every state exactly one mode:
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); // 1 element
}
...
rdev->pm.power_state[state_index].num_clock_modes = 1;
With num_clock_modes == 1, current_clock_mode_index is necessarily 0, so
requested_clock_mode_index is always 0 entering the NO_DISPLAY check. The
RADEON_PM_MODE_NO_DISPLAY flag is set by default on state 0's single mode
(radeon_atombios.c:2075-2078), and the discrete branch selects
requested_power_state_index = 1 (single-head, r600.c:461-462) or the
multi-head fallback 0 (r600.c:458-460). If the selected state's mode 0
carries NO_DISPLAY, the ++ yields index 1 == num_clock_modes β exactly
one past the end of a 1-element allocation.
OOB consumers (the read sites)
r600.c:516-522βDRM_DEBUG_DRIVER("Requested: e: %d m: %d p: %d\n", power_state[req_ps].clock_info[req_cm].sclk, ... .mclk, ...)β C function arguments are evaluated even when debug printing is disabled β unconditional OOB heap read.radeon_pm.c:181-199(radeon_set_power_state) βclock_info[req_cm].sclkand.mclkOOB; the garbage values are clamped only upward (> default_sclk), then programmed viaradeon_set_engine_clock()/radeon_set_memory_clock()(radeon_pm.c:222-237).r600_pm_misc()r600.c:766-783βps->clock_info[req_cm].voltageOOB; a garbageVOLTAGE_SWvoltage is passed toradeon_atom_set_voltage()(r600.c:777-778) β arbitrary voltage index programmed to the GPU.
Impact ceiling: OOB kernel heap read (limited scope, kmalloc slab adjacent data) whose contents drive GPU clock/voltage programming β local DoS / hardware misbehavior class. No direct kernel-memory disclosure to userspace (values go to GPU registers), hence Low severity β consistent with the pass-1 rating of the sibling DF-1196 (Low).
Reachability analysis (DragonFly-specific)
r600_pm_get_dynpm_stateis only called viaradeon_pm_get_dynpm_statefrom the PM worker underpm_method == PM_METHOD_DYNPM(radeon_pm.c:1765/1777/1800/1948), all behindradeon_pm_compute_clocks_old'snum_power_states < 2guard (radeon_pm.c:1735).- The only write of
PM_METHOD_DYNPMis the sysfs storeradeon_set_pm_method(radeon_pm.c:444-449), and no sysfs power attributes are created on DragonFly β both creation blocks are#if 0'd (radeon_pm.c:1603-1616for the DPM attrs,1638-1647for power_profile/power_method).pm_methodis only ever PROFILE (default) or DPM (radeon_pm.c:1522-1588). - Therefore on this tree the DYNPM branch is dead code today β the defect
is latent and becomes live the moment the sysfs knob is re-enabled (a
one-line
#if 1), or in upstream-derived trees. This is exactly the same reachability posture as the accepted DF-1196 (same entry path), filed as a finding by pass 1 for the same reason.
Guest verification performed
env.txt / run.log: DragonFly 6.5-DEVELOPMENT x86_64 guest;
ls /boot/kernel/radeonkms.ko β No such file (driver not built for this
guest kernel; only the 224 radeonkmsfw_*.ko firmware stubs exist);
pciconf -l | grep 1002 β no AMD device (virtio-only). A runtime trigger is
impossible on this guest; no kernel build/fix validation was attempted for
the same reason (fix_status: not_testable).
Recommended fix
Bounds-check the increment at r600.c:486 (see fix.diff):
only increment while
requested_clock_mode_index < power_state[requested_power_state_index].num_clock_modes - 1.
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_testablefix.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-3734/fix.diff
Confirmed kernel references
- sys/dev/drm/radeon/r600.c:486
- sys/dev/drm/radeon/r600.c:469-487
- sys/dev/drm/radeon/r600.c:516-522
- sys/dev/drm/radeon/r600.c:766-783
- sys/dev/drm/radeon/radeon.h:1300-1311
- sys/dev/drm/radeon/radeon_atombios.c:2126-2133
- sys/dev/drm/radeon/radeon_atombios.c:2075-2078
- sys/dev/drm/radeon/radeon_pm.c:181-199
- sys/dev/drm/radeon/radeon_pm.c:444-449
- sys/dev/drm/radeon/radeon_pm.c:1603-1616
Detail
Evidence (decisive lines)
VERDICT.md (full source-level trace with path:line); fix.diff (bounds-checked increment); run.log + env.txt (guest has no AMD GPU and no radeonkms.ko β 224 radeonkmsfw_*.ko firmware stubs only); decisive lines: r600.c:486 (unchecked ++), radeon.h:1300-1311 (clock_info is per-state heap pointer), radeon_atombios.c:2126-2133 (1-element allocation, num_clock_modes=1), radeon_atombios.c:2075-2078 (NO_DISPLAY on state 0 by default), r600.c:516-522 + r600.c:771 + radeon_pm.c:181-199 (OOB consumers).
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
Bounds-check requested_clock_mode_index++ at r600.c:486 against power_state[requested_power_state_index].num_clock_modes (see fix.diff).
Verdict
Source-certain one-past-end OOB heap read: r600.c:486 increments requested_clock_mode_index in the discrete-branch DYNPM downclock path without checking num_clock_modes; with PowerPlayInfo frev1/2 BIOS tables every state has exactly one clock mode (radeon_atombios.c:2127-2133), so the increment yields index 1 == mode count and clock_info[1] is read past a 1-element kzalloc at r600.c:516-522 (DRM_DEBUG_DRIVER args are evaluated even with printing off) and via r600_pm_misc/radeon_set_power_state (r600.c:771, radeon_pm.c:181-199), programming garbage clocks/voltage. 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). On DragonFly the DYNPM method is additionally unreachable today because all PM sysfs attribute creation is compiled out (radeon_pm.c:1603/1638 #if 0), making the defect latent here but identical in reachability posture to accepted sibling DF-1196 (which is the power_state index at :405 in the IGP branch; DF-3734 is the clock_mode index at :486 in the discrete branch β different variable, array, and bounds source, not a duplicate).
No comments yet.