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

Heap OOB read via unbounded vddInd/vddciInd/mvddInd into 4-8 entry voltage lookup tables

Summary

vega10_patch_voltage_dependency_tables_with_lookup_table (:635-681), vega10_populate_single_display_type (:1854-1863), vega10_get_clock_by_type_with_voltage (:4246-4251): vddInd/vddciInd/mvddInd (u8 from VBIOS records, 0-255) used to index vddc_lookup_table (8 slots), vddci_lookup_table (4 slots), vddmem_lookup_table (4 slots) with NO bounds check. OOB read of ~1KB past 4-8 entry tables into adjacent kernel heap. Read values (us_vdd) propagate to SMC and sysfs -> info leak. Fix: bounds-check against table->count.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1181 Β· 12 files
FileTypeDescriptionSize
harness.c trigger-source userspace transcription of the vddInd/vddciInd/mvddInd indexing flaw 4.9 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.6 KB ↓ raw
VERDICT.md verdict detailed mechanism, source trace, why-not-reproduced 3.6 KB ↓ raw
fix.diff suggested-fix add 7 bounds checks (vddInd/vddciInd/mvddInd/vddcInd < *_lookup_table->count) at all 3 functions 2.7 KB view raw
run.log run-log harness stdout showing OOB reads at vddci[100], vddmem[255] etc 656 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) 128 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-1181 β€” vega10 VBIOS vddInd/vddciInd/mvddInd OOB read

Bug (confirmed in source)

sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.c β€” three call sites read the per-entry vddInd / vddciInd / mvddInd fields as uint8_t (0-255 range, supplied by the VBIOS) and use them to index into fixed-size voltage lookup tables with no bounds check against the table's count or its max_levels allocation:

  • vega10_patch_voltage_dependency_tables_with_lookup_table() lines 659, 666, 672, 675, 678 β€” five index sites in one function.
  • vega10_populate_single_display_type() lines 1856-1857.
  • vega10_get_clock_by_type_with_voltage() lines 4248-4249.

Allocation (in sys/dev/drm/amd/powerplay/hwmgr/vega10_processpptables.c):

// get_vddc_lookup_table() at lines 1026-1034
table_size = sizeof(uint32_t) + sizeof(phm_ppt_v1_voltage_lookup_record) * max_levels;
table = kzalloc(table_size, GFP_KERNEL);
table->count = vddc_lookup_pp_tables->ucNumEntries;   // from VBIOS u8

// callers (lines 1108-1127) pass:
//   vddc_lookup_table   max_levels = 8
//   vddmem_lookup_table max_levels = 4
//   vddci_lookup_table  max_levels = 4

A malicious VBIOS that supplies vddciInd = 100 therefore indexes vddci_lookup_table->entries[100], reading 100 * sizeof(phm_ppt_v1_voltage_lookup_record) (= 100 Γ— 10 = 1000 bytes) past the 4-entry (40-byte) allocation. Read values (us_vdd) propagate into the SMC firmware and into the sysfs power-state tables β€” an info leak of adjacent kernel heap, plus corrupted power-management state.

Trigger model

  • Requires amdgpu attached to a real AMD Vega10 GPU (powerplay hwmgr init path). Not present on this guest.
  • CVSS AV:L/AC:L/PR:L/UI:N/S:U:C:H/I:N/A:H β€” the analyst's PR:L assumes an unprivileged user could supply a crafted VBIOS / PowerPlay table via sysfs; on this code the table is parsed from the device's own firmware at attach time, so in practice the trigger is PR:H (root to flash malicious firmware). The bug class is real either way.

On this guest

Unreachable β€” no AMD GPU; amdgpu.ko is present but never attaches. harness.c is a userspace transcription of the indexing logic; it builds, runs, and prints which indices would OOB-read, but produces no kernel effect.

Reproduce

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

Bounds-check every vddInd / vddciInd / mvddInd / vddcInd against the corresponding *_lookup_table->count before indexing; return -EINVAL (or -1 for the void-return-styled helpers) on violation. Full patch in fix.diff. Matches the finding proposal.

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

DF-1181 β€” 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)

The powerplay Vega10 hwmgr patches VBIOS dependency tables by reading per-entry voltage indices and looking them up in fixed-size allocation tables. All index fields are uint8_t from the VBIOS:

vega10_hwmgr.c:658-680 (inside vega10_patch_voltage_dependency_tables_with_lookup_table):

for (entry_id = 0; entry_id < vdt->count; entry_id++) {
    voltage_id = vdt->entries[entry_id].vddInd;          // u8
    vdt->entries[entry_id].vddc =
        table_info->vddc_lookup_table->entries[voltage_id].us_vdd;   // no check
}
...
for (entry_id = 0; entry_id < mm_table->count; ++entry_id) {
    voltage_id = mm_table->entries[entry_id].vddcInd;     // u8
    mm_table->entries[entry_id].vddc =
        table_info->vddc_lookup_table->entries[voltage_id].us_vdd;   // no check
}
for (entry_id = 0; entry_id < mclk_table->count; ++entry_id) {
    voltage_id = mclk_table->entries[entry_id].vddInd;        // u8
    mclk_table->entries[entry_id].vddc =
        table_info->vddc_lookup_table->entries[voltage_id].us_vdd;
    voltage_id = mclk_table->entries[entry_id].vddciInd;      // u8
    mclk_table->entries[entry_id].vddci =
        table_info->vddci_lookup_table->entries[voltage_id].us_vdd;
    voltage_id = mclk_table->entries[entry_id].mvddInd;       // u8
    mclk_table->entries[entry_id].mvdd =
        table_info->vddmem_lookup_table->entries[voltage_id].us_vdd;
}

vega10_hwmgr.c:1856-1857 (vega10_populate_single_display_type):

vddc = table_info->vddc_lookup_table->
    entries[dep_table->entries[i].vddInd].us_vdd;   // no check

vega10_hwmgr.c:4248-4249 (vega10_get_clock_by_type_with_voltage):

clocks->data[i].voltage_in_mv = (uint32_t)(table_info->vddc_lookup_table->
    entries[dep_table->entries[i].vddInd].us_vdd);   // no check

Allocation sizing in vega10_processpptables.c:1014-1043 (get_vddc_lookup_table):

table_size = sizeof(uint32_t) +
    sizeof(phm_ppt_v1_voltage_lookup_record) * max_levels;
table = kzalloc(table_size, GFP_KERNEL);
table->count = vddc_lookup_pp_tables->ucNumEntries;

Callers at vega10_processpptables.c:1108-1127 pass max_levels = 8 for vddc, 4 for vddmem, 4 for vddci. With vddciInd = 100 from the VBIOS the kernel reads ~1000 bytes past the 4-entry (40-byte) allocation β€” CWE-125 heap OOB read. Read values flow back to the SMC firmware and into sysfs.

Why it cannot be reproduced on this guest

  • No AMD GPU on the PCI bus (pciconf -l: vgapci0 is QEMU stdvga vendor 0x1234, not AMD).
  • amdgpu, radeon, drm, powerplay are not in X86_64_GENERIC; the powerplay module never attaches, so the Vega10 hwmgr init path that calls vega10_patch_voltage_dependency_tables_with_lookup_table is never taken.
  • Even with hardware present the trigger requires a malicious VBIOS (root or physical access to flash).

Latent / hardware-gated bug; confirmed real by source trace, unreachable on the audit guest, not an unprivileged-local-to-root escalation. harness.c is a userspace transcription of the indexing logic; it builds, runs, and prints which indices would OOB-read, but produces no kernel effect.

Fix

fix.diff adds if (index >= table->count) return -EINVAL/-1; before every vddInd/vddciInd/mvddInd/vddcInd index site (3 functions, 7 sites). Validated as applies + compiles only β€” the bug path cannot be exercised without AMD hardware, so fix_status: not_testable.

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 vddInd/vddciInd/mvddInd indices -> heap OOB read. amdgpu not in GENERIC.