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

Unbounded VCE clk_idx causes heap OOB read in kv_parse_power_table

Summary

kv_parse_power_table VCE loop at kv_dpm.c:2705-2707: clock_array_index=vce_states[i].clk_idx (6-bit 0-63 from VBIOS). clock_info=&clockInfo[clock_array_index*ucEntrySize]. NO check vs ucNumEntries. OOB read up to 16065 bytes past clockInfo[0]. Sibling of DF-1269 VCE clk_idx. Fix: check clock_array_index<ucNumEntries.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1307 Β· 13 files
FileTypeDescriptionSize
harness.c trigger-source replica of ClockInfoArray+VCE byte-index access; clk_idx=63*ucEntrySize=255 faults off 5-page guard 6.0 KB view raw
build.sh build-script cc -O2 -Wall -o harness harness.c 107 B view raw
run.sh run-script ./harness 60 B view raw
build.log build-log final successful build 65 B view raw
run.log run-log decisive run: FAULT/CONFIRMED 671 B view raw
fix.diff suggested-fix bounds-check VCE clk_idx vs ucNumEntries (mirror main clock loop) 1013 B view raw
env.txt environment uname, cc version 555 B view raw
VERDICT.md verdict full narrative: VCE clk_idx provenance, asymmetry vs clock loop, mechanism, harness, fix 3.9 KB ↓ raw
README.md readme build/run/expected 992 B ↓ raw
fix_module_proof.txt fix-build-proof radeon.ko built with fix applied under -Werror, 0 errors, kv_dpm.o produced 169 B view raw
fix_module_build.log fix-build-log radeon module build excerpt: kv_dpm.o compiled, radeon.ko linked 63.0 KB view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme build/run/expected
↓ download raw

DF-1307 β€” Unbounded VCE clk_idx heap OOB read in kv_parse_power_table

Severity: Medium Β· CWE: CWE-125 (Out-of-bounds Read) File: sys/dev/drm/radeon/kv_dpm.c:2705-2707

Build & run (radeon-DRM latent bug β€” no AMD GPU on guest, harness proof)

./build.sh   # cc -O2 -Wall -o harness harness.c
./run.sh     # ./harness

Expected output (bug present)

VBIOS VCE clk_idx = 63  (NO check vs ucNumEntries=2)
access byte offset = 63 * 255 = 16065  (past the 2 valid entries)
FAULT (signal 11): OOB read at clockInfo[16065] off the bios buffer
RESULT: OOB read CONFIRMED at kv_dpm.c:2707

Preconditions (kernel path)

radeon driver attach on Kabini/Kaveri/Mullins/Trinity APUs (VCE power-state fill from powerplay tables). Attacker controls the VBIOS image (clk_idx is a 6-bit field in the VCE state record, r600_dpm.c:1128). See VERDICT.md. The main clock loop at line 2683 checks bounds; the VCE loop (2705) does not β€” that asymmetry is the bug.

VERDICT.md verdict full narrative: VCE clk_idx provenance, asymmetry vs clock loop, mechanism, harness, fix
↓ download raw

DF-1307 β€” Unbounded VCE clk_idx heap OOB read in kv_parse_power_table

Verdict: REPRODUCED (source-level + harness) β€” latent radeon-DRM bug, heap OOB read

The radeon DPM code (kv_dpm.c) is part of the radeon DRM module, which is not in X86_64_GENERIC and no AMD GPU is present on the audit guest. It is a real latent bug confirmed by source trace and reproduced at the access-pattern level with a userspace harness.

The bug

sys/dev/drm/radeon/kv_dpm.c, kv_parse_power_table, VCE power-state fill loop, lines 2705-2707:

for (i = 0; i < RADEON_MAX_VCE_LEVELS; i++) {
    u32 sclk;
    clock_array_index = rdev->pm.dpm.vce_states[i].clk_idx;   /* 6-bit 0..63, VBIOS */
    clock_info = (union pplib_clock_info *)
        &clock_info_array->clockInfo[clock_array_index * clock_info_array->ucEntrySize]; /* NO bounds check */
    ...
}

clk_idx originates from the VBIOS VCE state record:

/* r600_dpm.c:1127-1128 */
rdev->pm.dpm.vce_states[i].clk_idx = state_entry->ucClockInfoIndex & 0x3f;  /* 6-bit, 0..63 */

It is used to byte-index clockInfo[] (a UCHAR flex[1] array, pptable.h:446-454) at offset clk_idx * ucEntrySize, with no bounds check against clock_info_array->ucNumEntries. With clk_idx up to 63 and ucEntrySize up to 255 (UCHAR), the byte offset reaches 63 * 255 = 16065 past clockInfo[0] β†’ heap OOB read off the kmalloc'd bios buffer.

Contrast β€” the main clock loop at the same function DOES check (line 2683):

clock_array_index = idx[j];
if (clock_array_index >= clock_info_array->ucNumEntries)   /* <-- bounds check */
    continue;

The VCE loop at 2705 was never given the equivalent guard. Sibling of DF-1269 and DF-1306 (the non-clock path, same function, same pattern of missing check).

Reachability / threat model

kv_parse_power_table runs at radeon driver attach on Kabini/Kaveri/ Mullins/Trinity (Southern/Sea Islands APUs). Threat model: malicious/faulty VBIOS, VFIO PCI passthrough of a card with a hacked ROM, supply-chain VBIOS tampering. Effect: kernel heap OOB read (info leak of adjacent slab/heap contents, up to ~16 KB) and/or DoS (read past mapped bios buffer β†’ panic). Local, requires attacker control of the VBIOS image β€” the same trust boundary the driver already assumes.

Harness proof

harness.c replicates ClockInfoArray + the VCE loop's byte-index access, places clockInfo[0] at the tail of a data page with a 5-page PROT_NONE guard region after it (the 16065-byte OOB would skip a single guard page), sets clk_idx=63 + ucEntrySize=255 (the crafted-VBIOS amplification), and shows the read at byte offset 16065 faults. Decisive run:

DF-1307 kv_parse_power_table VCE clk_idx OOB read harness
sizeof(ClockInfoArray) overhead = 3 (clockInfo is UCHAR flex[1])
ClockInfoArray @ 0x8004a0fee (clockInfo[0] @ 0x8004a0ff0), ucNumEntries=2, ucEntrySize=255
VBIOS VCE clk_idx = 63  (NO check vs ucNumEntries=2)
access byte offset = 63 * 255 = 16065  (past the 2 valid entries)
FAULT (signal 11): OOB read at clockInfo[16065] off the bios buffer
  -> in-kernel equivalent: heap OOB read up to 16065 bytes past clockInfo[0]
RESULT: OOB read CONFIRMED at kv_dpm.c:2707 (VCE clk_idx byte-index with NO bounds check vs ucNumEntries)

noinline + volatile inputs prevent gcc -O2 from constant-folding the array index and eliding the OOB load as UB.

Build & run

./build.sh   # cc -O2 -Wall -o harness harness.c
./run.sh     # ./harness

Fix

fix.diff adds the bounds check mirroring the clock path at line 2683, right before the unbounded byte-index access, and zeroes the VCE state on the error path (matching the loop's existing sclk=0; mclk=0; epilogue at line 2710-2711):

if (clock_array_index >= clock_info_array->ucNumEntries) {
    rdev->pm.dpm.vce_states[i].sclk = 0;
    rdev->pm.dpm.vce_states[i].mclk = 0;
    continue;
}

This matches the finding proposal ("check clock_array_index < ucNumEntries").

Fix verification

fixed

validated

module build rc=0 + harness
↓ fix.diffn/a (module build)

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source+harness. kv VCE clk_idx no bounds vs ucNumEntries -> 16KB OOB read. radeon not in GENERIC.