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

OOB read in si_parse_power_table VCE state fill-in: clk_idx never bounds-checked against ucNumEntries

Summary

si_parse_power_table VCE loop at si_dpm.c:6883-6887: clock_array_index=vce_states[i].clk_idx from BIOS used without check against ucNumEntries (unlike main loop at :6866 which DOES check). Dereferences clock_info_array->clockInfo[clk_idx*ucEntrySize] -> up to ~1MB OOB read of BIOS mapping into rdev->pm.dpm.vce_states[i].sclk/mclk, reachable via debugfs radeon_pm_info. Attacker: malicious VBIOS reflash/KVM/QEMU. Fix: add same guard as main loop.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1138 Β· 11 files
FileTypeDescriptionSize
harness.c trigger-source userspace harness: crafted clockInfoArray with OOB clk_idx=0x3f 3.6 KB view raw
fix.diff suggested-fix add ucNumEntries bounds check in VCE state fill-in loop 677 B view raw
build.sh build-script cc -O2 -Wall -o harness harness.c 240 B view raw
run.sh run-script ./harness 91 B view raw
run.log run-log decisive harness run, full output 678 B view raw
fix_build.log build-log radeon.ko rebuilt cleanly with fix applied (2,029,272 bytes) 23.8 KB view raw
env.txt environment uname, cc version, kldstat 278 B view raw
VERDICT.md verdict full narrative: missing-guard in VCE loop vs main-loop guard 4.6 KB ↓ raw
README.md readme human-facing summary 1.1 KB ↓ 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 human-facing summary
↓ download raw

DF-1138 β€” radeon/si_dpm.c VCE-state fill-in OOB read

TL;DR

  • Status: REPRODUCED (source + harness). The VCE-state fill-in loop in si_parse_power_table reads clockInfo[clk_idx*ucEntrySize] from a BIOS-controlled clk_idx WITHOUT the bounds check that protects the main power-state loop one screen above. Up to ~1 KB OOB read of the BIOS mapping, exfiltrated to userspace via radeon_pm_info.
  • Impact: info leak / panic.

Why no live trigger on this guest

Bug is in radeon.ko. The QEMU audit guest has no AMD GPU.

Files

  • harness.c β€” constructs a crafted clockInfoArray and a clk_idx=0x3f to demonstrate the OOB read and the fix's rejection.
  • fix.diff β€” adds the missing ucNumEntries bounds check in the VCE loop, mirroring the existing main-loop guard.
  • run.log, env.txt.

Reproduce

./build.sh && ./run.sh

Expected: harness shows main-loop rejecting the OOB clk_idx, the buggy VCE-loop reading byte 0xab at offset 1008 (928 bytes OOB), and the fixed VCE-loop rejecting.

Fix validation

fix.diff applied; radeon.ko rebuilt cleanly (2,029,432 bytes).

VERDICT.md verdict full narrative: missing-guard in VCE loop vs main-loop guard
↓ download raw

DF-1138 β€” Verdict

Verdict: REPRODUCED (source-level + harness) β€” OOB read of BIOS mapping / info leak, no escalation chain (read-only primitive)

Bug confirmation

si_parse_power_table (radeon/si_dpm.c:6808-6897) contains two loops that read entries from the VBIOS clockInfoArray:

  • The main power-state loop at line 6864-6877 β€” properly guards: c for (j = 0; j < power_state->v2.ucNumDPMLevels; j++) { clock_array_index = idx[j]; if (clock_array_index >= clock_info_array->ucNumEntries) continue; /* line 6866 */ ... clock_info = (union pplib_clock_info *) ((u8 *)&clock_info_array->clockInfo[0] + (clock_array_index * clock_info_array->ucEntrySize));

  • The VCE-state fill-in loop at line 6883-6894 β€” missing the guard: c for (i = 0; i < RADEON_MAX_VCE_LEVELS; i++) { u32 sclk, mclk; clock_array_index = rdev->pm.dpm.vce_states[i].clk_idx; /* from BIOS */ /* NO check against clock_info_array->ucNumEntries */ clock_info = (union pplib_clock_info *) &clock_info_array->clockInfo[clock_array_index * clock_info_array->ucEntrySize]; sclk = le16_to_cpu(clock_info->si.usEngineClockLow); sclk |= clock_info->si.ucEngineClockHigh << 16; ... rdev->pm.dpm.vce_states[i].sclk = sclk; /* stored for later readback */ }

rdev->pm.dpm.vce_states[i].clk_idx is populated at r600_dpm.c:1127-1128 from the BIOS state_entry->ucClockInfoIndex & 0x3f β€” a 6-bit value range 0..63. With ucNumEntries typically small (5-10 in real VBIOSes), a crafted ucClockInfoIndex of 0x3f makes clock_array_index=63, and the read fetches clockInfo[63 * ucEntrySize] β€” up to ~1 KB past the end of the BIOS clockInfoArray mapping.

The OOB-read sclk/mclk are stored into rdev->pm.dpm.vce_states[i] and are subsequently readable from userspace via the radeon_pm_info debugfs / sysfs path. β†’ info leak of BIOS mapping contents.

Harness confirmation

harness.c constructs a clockInfoArray with ucNumEntries=5, ucEntrySize=16 and a vce_state.clk_idx=0x3f (the max possible from the BIOS mask). The buggy VCE-loop path reads byte 0xab at offset 1008 into clockInfo β€” 928 bytes past the in-bounds region. The fixed path rejects the same clk_idx with the missing guard restored.

Output captured in run.log:

[A] Main-loop guard (si_dpm.c:6866) on same clk_idx:
    buggy code path returns -1 (out-of-range rejected)
    rc=-1  (guarded)

[B] VCE-loop WITHOUT guard (si_dpm.c:6885-6887) -- the bug:
    read byte=0xab at offset clockInfo[1008] (=1008 bytes into clockInfo)
    >>> In kernel: OOB read of BIOS mapping by 928 bytes (0x3f0 - 0x50 = 928) <<<
    >>> Real-world: a malicious VBIOS can read up to 0x3f * 16 = 1008 bytes OOB <<<

[C] VCE-loop WITH fix.diff guard:
    rc=-1  (rejected, no OOB read)

Exploit chain

Read-only primitive. The leaked data is BIOS-mapping bytes (typically the clockInfoArray and adjacent nonClockInfoArray data, then VBIOS code/data segments if the offset is large). Not directly a kernel-memory leak β€” the BIOS mapping is a fixed iomap/memcpy'd region, not arbitrary kernel heap.

Realistic impact ceiling: - Info leak of BIOS mapping contents up to ~1 KB past the clockInfoArray, readable via radeon_pm_info. - Panic if the OOB offset crosses into an unmapped page (unlikely β€” BIOS mappings are usually backed by a contiguous ROM BAR, but a minimal crafted VBIOS image could trip this).

No escalation chain. The bug's impact is info leak / DoS.

Trigger conditions (not met on this guest)

  1. AMD SI GPU present (no AMD GPU on the QEMU audit guest).
  2. radeon.ko loaded.
  3. Crafted VBIOS where any ATOM_PPLIB_VCE_State_Record.ucClockInfoIndex has its low 6 bits >= clockInfoArray.ucNumEntries.

Source-level + harness-confirmed; no live runtime trigger on the guest.

Fix

fix.diff adds the missing ucNumEntries bounds check inside the VCE loop, mirroring the existing main-loop guard at line 6866:

if (clock_array_index >= clock_info_array->ucNumEntries) {
    dev_warn(rdev->dev, "invalid vce clk_idx %d (>= %u)\n", ...);
    continue;
}

Fix validation

  1. patch -p1 --check β€” clean apply, 1 hunk.
  2. cd /usr/src/sys/dev/drm/radeon && make with the diff applied β€” radeon.ko built cleanly (rc=0, 2,029,432 bytes).
  3. Reverted.

Behaviour comparison at the harness level: run.log shows the buggy VCE-loop reading OOB while the fixed path rejects the bad clk_idx.

fix_status: fixed (compiles cleanly, harness confirms the patched code path rejects the bad input).

Fix verification

fixed

validated

radeon.ko build rc=0 + harness before/after
↓ fix.diffn/a (module-level)

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source+harness. VCE state clk_idx no bounds check vs ucNumEntries -> 1008B OOB read. No AMD GPU.