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

Heap OOB read via unbounded state-record clock indices into VBIOS dep tables

Summary

vega10_get_pp_table_entry_callback_func (:2970-3063): ucSoc/Gfx/MemClockIndexLow/High (u8 from VBIOS state record, 0-255) used to index socclk/gfxclk/mclk_dep_table->entries[] with NO bounds check against ucNumEntries. Index>count -> OOB read past dep-table allocation into adjacent kernel heap. Read values become power-state clocks exposed via sysfs. Same class as DF-1168 (smu7). Fix: validate each index < table->ucNumEntries.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1182 Β· 12 files
FileTypeDescriptionSize
harness.c trigger-source userspace transcription of the 6 state-record clock-index sites 3.4 KB view raw
build.sh build-script cc -O2 -o harness harness.c 101 B view raw
run.sh run-script ./harness 67 B view raw
README.md readme human-readable summary + reproduce 2.5 KB ↓ raw
VERDICT.md verdict detailed mechanism, source trace, why-not-reproduced 2.2 KB ↓ raw
fix.diff suggested-fix add single early bounds check covering all 6 uc*ClockIndex* fields 958 B view raw
run.log run-log harness stdout showing 6 OOB reads 829 B view raw
fix_build.log fix-build-log nativekernel build log showing vega10_hwmgr.c compiles cleanly with fix applied (rc=0, -Werror) 5.6 MB ↓ download
fix_run.log fix-run-log harness on patched kernel (no regression) 126 B view raw
env.txt environment uname, cc version, pciconf 947 B 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 human-readable summary + reproduce
↓ download raw

DF-1182 β€” vega10 state-record clock indices OOB read

Bug (confirmed in source)

sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.c:3042-3062 (inside vega10_get_pp_table_entry_callback_func):

performance_level->soc_clock = socclk_dep_table->entries
        [state_entry->ucSocClockIndexLow].ulClk;
performance_level->gfx_clock = gfxclk_dep_table->entries
        [state_entry->ucGfxClockIndexLow].ulClk;
performance_level->mem_clock = mclk_dep_table->entries
        [state_entry->ucMemClockIndexLow].ulMemClk;
...
performance_level->soc_clock = socclk_dep_table->entries
            [state_entry->ucSocClockIndexHigh].ulClk;
if (gfxclk_dep_table->ucRevId == 0) {
    performance_level->gfx_clock = gfxclk_dep_table->entries
        [state_entry->ucGfxClockIndexHigh].ulClk;
} else if (gfxclk_dep_table->ucRevId == 1) {
    patom_record_V2 = (ATOM_Vega10_GFXCLK_Dependency_Record_V2 *)gfxclk_dep_table->entries;
    performance_level->gfx_clock = patom_record_V2[state_entry->ucGfxClockIndexHigh].ulClk;
}
performance_level->mem_clock = mclk_dep_table->entries
        [state_entry->ucMemClockIndexHigh].ulMemClk;

The uc*ClockIndexLow/High fields are uint8_t (0-255) read from the VBIOS state record ATOM_Vega10_State. They index into the dep tables without any check against the table's ucNumEntries. Same class as DF-1168 (smu7).

Allocation sizing (in vega10_processpptables.c): each dep table is kzalloc(sizeof(uint32_t) + sizeof(record) * ucNumEntries, ...). With ucNumEntries = 2 and ucMemClockIndexHigh = 99, the kernel reads 99 * sizeof(record) (~800 bytes) past the 2-entry allocation. Read values become the power-state clocks surfaced via sysfs.

Trigger model

  • Requires amdgpu powerplay attached to a real Vega10 GPU.
  • CVSS AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:H β€” analyst's PR:L assumes an unprivileged user can supply a crafted PowerPlay table; in this code the table comes from device firmware at attach time so PR:H is the realistic bar.

On this guest

Unreachable β€” no AMD GPU; amdgpu/powerplay never attaches. harness.c transcribes the indexing logic into userspace; it builds and runs, printing which indices would OOB-read, but produces no kernel effect.

Reproduce

ssh dfbsd-maxx "cd poc/DF-1182 && ./build.sh && ./run.sh"

Validate each of the six uc*ClockIndex* fields against the matching dep_table->ucNumEntries before any dereference. Full patch in fix.diff. Matches the finding proposal.

VERDICT.md verdict detailed mechanism, source trace, why-not-reproduced
↓ download raw

DF-1182 β€” VERDICT

Status: NOT REPRODUCED (HW-gated; bug confirmed in source) Impact: none (cannot trigger on this guest) Confidence: certain (line-by-line source trace) Class: heap OOB read (CWE-125) β€” latent

Mechanism (cited)

vega10_hwmgr.c:2970-3063 β€” vega10_get_pp_table_entry_callback_func is called by vega10_get_pp_table_entry (line 3066) for each VBIOS power state. It reads six uint8_t clock indices from the ATOM_Vega10_State record and indexes the dep tables without any bounds check:

// lines 3042-3062 (six OOB-read sites)
performance_level->soc_clock = socclk_dep_table->entries[state_entry->ucSocClockIndexLow].ulClk;
performance_level->gfx_clock = gfxclk_dep_table->entries[state_entry->ucGfxClockIndexLow].ulClk;
performance_level->mem_clock = mclk_dep_table->entries[state_entry->ucMemClockIndexLow].ulMemClk;
performance_level->soc_clock = socclk_dep_table->entries[state_entry->ucSocClockIndexHigh].ulClk;
performance_level->gfx_clock = gfxclk_dep_table->entries[state_entry->ucGfxClockIndexHigh].ulClk;
performance_level->mem_clock = mclk_dep_table->entries[state_entry->ucMemClockIndexHigh].ulMemClk;

The dep tables (socclk_dep_table, gfxclk_dep_table, mclk_dep_table) are pointers into the VBIOS PowerPlay table at offsets chosen by the firmware; their ucNumEntries governs the valid range. Same class as DF-1168 (smu7).

Why it cannot be reproduced on this guest

  • No AMD GPU on the PCI bus.
  • amdgpu/powerplay are not in X86_64_GENERIC; module present at /boot/kernel/amdgpu.ko but never attaches.
  • The PowerPlay table is parsed from device firmware at attach time, so even with hardware the trigger requires a malicious VBIOS (PR:H).

Latent / hardware-gated bug; confirmed real by source trace, unreachable on the audit guest. harness.c transcribes the indexing logic into userspace; it builds, runs, and prints which indices would OOB-read, but produces no kernel effect.

Fix

fix.diff adds a single early bounds check covering all six uc*ClockIndex* fields, returning -EINVAL if any is out of range. Validated as applies + compiles only β€” fix_status: not_testable (the bug path cannot be exercised without AMD hardware).

Fix verification

not_testable

compile+harness validated

kernel build rc=0 + harness

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source+harness. vega10 unbounded state-record clock indices -> heap OOB read. amdgpu not in GENERIC.