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

Unbounded nonClockInfoIndex causes heap OOB read in kv_parse_power_table

Summary

kv_parse_power_table at kv_dpm.c:2668-2670: non_clock_array_index=power_state->v2.nonClockInfoIndex (u8 0-255 from VBIOS) used directly to index non_clock_info_array->nonClockInfo[non_clock_array_index]. NO bounds check vs ucNumEntries (clock path at :2683 DOES check). nonClockInfo is flex[1], OOB read up to 6120 bytes past nonClockInfo[0]. Sibling of DF-1269. Fix: check non_clock_array_index<ucNumEntries.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1306 Β· 13 files
FileTypeDescriptionSize
harness.c trigger-source replica of NonClockInfoArray+ATOM_PPLIB_NONCLOCK_INFO; nonClockInfoIndex=255 faults off buffer 5.8 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 679 B view raw
fix.diff suggested-fix bounds-check non_clock_array_index vs ucNumEntries (mirror clock path) 918 B view raw
env.txt environment uname, cc version 555 B view raw
VERDICT.md verdict full narrative: asymmetry vs clock path, mechanism, harness, fix 3.9 KB ↓ raw
README.md readme build/run/expected 852 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-1306 β€” Unbounded nonClockInfoIndex 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:2668-2670

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 nonClockInfoIndex = 255  (NO check vs ucNumEntries=1)
FAULT (signal 11): OOB read at nonClockInfo[255] off the bios buffer
RESULT: OOB read CONFIRMED at kv_dpm.c:2670

Preconditions (kernel path)

radeon driver attach on Kabini/Kaveri/Mullins/Trinity APUs (powerplay table parse). Attacker controls the VBIOS image. See VERDICT.md. The clock path at line 2683 checks bounds; the non-clock path (2668) does not β€” that asymmetry is the bug.

VERDICT.md verdict full narrative: asymmetry vs clock path, mechanism, harness, fix
↓ download raw

DF-1306 β€” Unbounded nonClockInfoIndex 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, lines 2668-2670:

non_clock_array_index = power_state->v2.nonClockInfoIndex;   /* u8 0..255, VBIOS */
non_clock_info = (struct _ATOM_PPLIB_NONCLOCK_INFO *)
    &non_clock_info_array->nonClockInfo[non_clock_array_index];   /* NO bounds check */

non_clock_array_index comes straight from the VBIOS (ATOM_PPLIB_STATE_V2. nonClockInfoIndex, a u8) and is used to index nonClockInfo[] with no bounds check against non_clock_info_array->ucNumEntries.

nonClockInfo is a flex[1] array of ATOM_PPLIB_NONCLOCK_INFO entries (pptable.h:298-309, 24 bytes packed; pptable.h:456-464). With nonClockInfoIndex=255 and ucNumEntries=1, the access reads at byte offset 255 * 24 = 6120 past nonClockInfo[0] β†’ heap OOB read off the kmalloc'd bios buffer.

Contrast β€” the clock path 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 non-clock path (2668-2670) was simply never given the equivalent guard β€” this is the regression/inconsistency that the finding calls out. Sibling of DF-1269 (same pattern in other radeon DPM drivers) and DF-1307 (the VCE loop two lines down, same function).

Reachability / threat model

kv_parse_power_table runs at radeon driver attach on Kabini/Kaveri/ Mullins/Trinity (Southern/Sea Islands APUs), parsing the powerplay tables from the GPU VBIOS. 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) 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 NonClockInfoArray + ATOM_PPLIB_NONCLOCK_INFO verbatim, places nonClockInfo[0] at the tail of a page-backed region with the next page unmapped, sets nonClockInfoIndex=255 (the crafted-VBIOS value), and shows the read at nonClockInfo[255] faults off the buffer. Decisive run:

DF-1306 kv_parse_power_table nonClockInfoIndex OOB read harness
sizeof(ATOM_PPLIB_NONCLOCK_INFO)=28  sizeof(NonClockInfoArray)=32
NonClockInfoArray @ 0x80047cfe0 (nonClockInfo[0] @ 0x80047cfe4), ucNumEntries=1
VBIOS nonClockInfoIndex = 255  (NO check vs ucNumEntries=1)
access byte offset = 255 * 28 = 7140  (past the single valid entry)
FAULT (signal 11): OOB read at nonClockInfo[255] off the bios buffer
  -> in-kernel equivalent: heap OOB read up to 7140 bytes past nonClockInfo[0]
RESULT: OOB read CONFIRMED at kv_dpm.c:2670 (nonClockInfoIndex used with NO bounds check vs ucNumEntries)

(28 bytes/entry here is the unpacked size; the kernel ATOM struct is packed to 24, giving the finding's 6120-byte ceiling β€” same bug either way.)

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 index is used, and frees the already-allocated dpm.ps on the error path (matching the existing kfree(rdev->pm.dpm.ps); return -ENOMEM; pattern at line 2675):

if (non_clock_array_index >= non_clock_info_array->ucNumEntries) {
    kfree(rdev->pm.dpm.ps);
    return -EINVAL;
}

This matches the finding proposal ("check non_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 nonClockInfoIndex no bounds vs ucNumEntries -> 6KB OOB read. radeon not in GENERIC.