Heap buffer overflow via unbounded ucStateEntrySize in power table parsing
Summary
ni_parse_power_table() at ni_dpm.c:4030: loop uses VBIOS-controlled ucStateEntrySize (u8, 0-255) as bound, passes loop index j directly to ni_parse_pplib_clock_info() which indexes performance_levels[NISLANDS_MAX_SMC_PERFORMANCE_LEVELS_PER_SWSTATE=16] at :3927. No bounds check before call. ucStateEntrySize>=18 overflows heap kzalloc(sizeof(struct ni_ps)~324B) with attacker-controlled clock/voltage values. Sibling of DF-1136/DF-1141/DF-1166/DF-1179 VBIOS dpm_levels overflow pattern. Requires crafted VBIOS (physical access or privileged flash). Fix: check j>=NISLANDS_MAX_SMC_PERFORMANCE_LEVELS_PER_SWSTATE before call.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1203 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | full path:line trace + reachability analysis | 3.8 KB | β raw |
| README.md | readme | claim, verdict, threat model, fix | 2.2 KB | β raw |
| fix.diff | suggested-fix | cap loop at NISLANDS_MAX_SMC_PERFORMANCE_LEVELS_PER_SWSTATE (16) | 679 B | view raw |
| run.sh | reachability-probe | shell probe: is radeon parser reachable on this guest? | 816 B | view raw |
| build.sh | build-noop | no userspace build (input is GPU VBIOS, not a syscall) | 307 B | view raw |
| env.txt | environment | guest uname, cc, device topology | 718 B | view raw |
| fix_build.log | build-log | compile-validation: kernel+module build with fix applied, rc=0, no errors | 139.5 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 |
DF-1203 β Heap overflow via unbounded ucStateEntrySize (radeon ni_dpm.c)
Claim
ni_parse_power_table() (sys/dev/drm/radeon/ni_dpm.c:4030) loops
for (j = 0; j < ucStateEntrySize - 1; j++) driven by a VBIOS-supplied
u8 ucStateEntrySize (0-255) and passes the loop index j straight into
ni_parse_pplib_clock_info(... index=j), which indexes
ps->performance_levels[index] at ni_dpm.c:3927. That array has exactly
NISLANDS_MAX_SMC_PERFORMANCE_LEVELS_PER_SWSTATE = 16 slots
(nislands_smc.h:28). The backing object is a heap kzalloc(sizeof(struct
ni_ps)) (ni_dpm.c:4020). A VBIOS with ucStateEntrySize >= 17 therefore
writes attacker-chosen clock/voltage fields past the end of the struct
ni_ps heap object.
Verdict
NOT TESTABLE on this audit guest (real bug, traced line-by-line in
sys/, fix authored and compile-validated). See VERDICT.md.
Why not reproduced here
radeonis not in the default kernel config (sys/config/X86_64_GENERIChas nodevice radeon); it isoptional radeon drminsys/conf/files(a loadable KLD, present only inLINT64).- The guest has no AMD radeon GPU (
pciconfshows only the QEMU stdvga0x11111234at pci0:0:2:0), soradeon.kocannot attach andni_dpm_init()/ni_parse_power_table()never run. kldloadofradeon.korequires root (forbidden precondition) and still needs an AMD GPU to call the parser.
Trigger (threat model)
A malicious or buggy GPU VBIOS AtomPowerTable with pplib.ucStateEntrySize
set >= 17. Parsed on any system that loads the radeon DRM module against an
AMD Northern Islands (Cayman/Barts/Turks/Antilles) GPU. Impact: kernel heap
overflow of sizeof(struct rv7xx_pl) per extra slot, content = GPU-supplied
clock/voltage/flags β corruption / DoS on default GENERIC; primitive-only on
unhardened builds.
Reproduce (on appropriate hardware)
On a host with an AMD NI-class GPU + radeon DRM loaded, a crafted VBIOS image flashed/supplied to the GPU triggers the overflow during DPM init. No userspace syscall reaches this path; the input is the GPU firmware table, so there is no runnable userspace PoC.
Fix
fix.diff bounds the loop index to
NISLANDS_MAX_SMC_PERFORMANCE_LEVELS_PER_SWSTATE, matching the existing
post-check at ni_dpm.c:2641.
DF-1203 β VERDICT
Finding: Heap buffer overflow via unbounded ucStateEntrySize in radeon
power-table parsing (sys/dev/drm/radeon/ni_dpm.c).
Status: NOT TESTABLE on this audit guest. Confidence (bug is real): certain.
Impact ceiling: kernel heap overflow (corruption / DoS); device/firmware-controlled.
Fix: authored in fix.diff, applies clean, compile-validated against the
radeon KLD source (see fix_build.log).
Mechanism (confirmed line-by-line in sys/)
ni_dpm_init()βni_parse_power_table()(ni_dpm.c:3993) reads the AtomPowerTable from the GPU VBIOS.ni_dpm.c:4020βps = kzalloc(sizeof(struct ni_ps), GFP_KERNEL)allocates one power-state object.struct ni_ps(ni_dpm.h:172) holdsperformance_levels[NISLANDS_MAX_SMC_PERFORMANCE_LEVELS_PER_SWSTATE], and that constant is 16 (nislands_smc.h:28).ni_dpm.c:4030βfor (j = 0; j < (power_info->pplib.ucStateEntrySize - 1); j++).ucStateEntrySizeis au8taken verbatim from the VBIOS (0-255).jisint(ni_dpm.c:3990), so no underflow whenucStateEntrySize == 0.ni_dpm.c:4035-4037β callsni_parse_pplib_clock_info(rdev, &ps[i], j, clock_info).ni_dpm.c:3927βstruct rv7xx_pl *pl = &ps->performance_levels[index];withindex == j. No bounds check.- For
ucStateEntrySize >= 17,jreaches 16 andperformance_levels[16]overflows the heap object. Each extra slot writessizeof(struct rv7xx_pl)of VBIOS-controlledsclk/mclk/vddc/vddci/flags(ni_dpm.c:3931-3938).
The same code already defends against over-count later at ni_dpm.c:2641
(if (state->performance_level_count > NISLANDS_MAX_SMC_PERFORMANCE_LEVELS_PER_SWSTATE)),
confirming 16 is the intended bound β it is simply never enforced at the
parsing loop.
Why it is NOT TESTABLE on this guest
sys/config/X86_64_GENERICcontains nodevice radeon;radeonisoptional radeon drminsys/conf/files(loadable module, inLINT64only).pciconf -lvon the guest shows onlyvgapci0@pci0:0:2:0chip0x11111234(QEMU Standard VGA) β no AMD GPU, soradeon.kocannot attach and the DPM/power-table parser is dead code at runtime here.- The input that drives the bug (the AtomPowerTable) lives in the GPU VBIOS,
not in any userspace syscall argument; there is no unprivileged path to
supply it, and
kldload radeonis a root-only action that still requires the matching hardware.
This is the valid "dead/unreachable at runtime on this guest" case from the procedure. The bug is genuine (path:line confirmed above); it is simply latent on a guest lacking the device. Per the realism test, the trigger precondition ("a radeon GPU with a malicious/crafted VBIOS") is a plausible real-world threat (malicious peripheral / tampered firmware), not an unprivileged-user escalation.
Exploit chain
None developed: the primitive is reachable only via GPU firmware on a system with the specific hardware and the radeon module loaded. There is no unprivileged local path to it on this guest, so there is no unprivβroot chain to build here. Documented impact ceiling: heap corruption / DoS from a malicious VBIOS.
Fix
fix.diff caps the loop index at the array bound:
for (j = 0;
j < (power_info->pplib.ucStateEntrySize - 1) &&
j < NISLANDS_MAX_SMC_PERFORMANCE_LEVELS_PER_SWSTATE;
j++) {
Minimal, targeted at the root cause, and consistent with the existing 16-slot invariant. Supersedes the finding proposal's intent with an exact, line-accurate diff.
Build / run on this guest
./build.sh && ./run.sh runs a reachability probe (checks for an AMD radeon
GPU + the radeon module). On this guest it reports "no AMD GPU / radeon not
loaded β parser unreachable"; the bug itself is confirmed by the source trace
above and the compile-validated fix.diff.
Fix verification
not_testablecompile validated -Werror
module/kernel build rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed. ni_parse_power_table ucStateEntrySize u8 unbounded vs performance_levels[16]. radeon not in GENERIC.
No comments yet.