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

Heap buffer overflow via state_index/i index mismatch in radeon_atombios_parse_power_table_6

Summary

radeon_atombios_parse_power_table_6 at :2698-2735: loop allocates clock_info per index i (state ucNumDPMLevels) but writes via state_index which lags when prior state has zero valid clock modes. state_index<i -> writes to power_state[state_index].clock_info sized for a DIFFERENT state -> heap overflow. Attacker crafts VBIOS with state[0] having 1 invalid DPM level (stall state_index) then state[1] with N valid levels -> overflow power_state[0].clock_info[1..N] by (N-1)*sizeof(pm_clock_info) bytes. Malicious PCIe GPU/VFIO passthrough at driver probe. Fix: use i instead of state_index for all writes within loop body.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1198 Β· 11 files
FileTypeDescriptionSize
harness.c trigger-source replica of power_table_6 loop: alloc[i] vs write[state_index] mismatch -> overflow 5.1 KB view raw
VERDICT.md verdict full narrative + fix (alloc at state_index, free on skip) 4.0 KB ↓ raw
build.sh build-script cc -O2 -Wall -o harness harness.c 172 B view raw
run.sh run-script ./harness 66 B view raw
run.log run-log 3 OOB clock_info writes, 48-byte canary clobber 732 B view raw
env.txt environment uname, cc version 418 B view raw
fix.diff suggested-fix allocate clock_info at state_index (not i); free on zero-valid-modes skip 1.8 KB view raw
fix_build.log build-log radeon.ko rebuilt with both radeon fixes, -Werror, rc=0 143.5 KB view raw
README.md readme human reproduce doc 1.4 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 reproduce doc
↓ download raw

DF-1198 β€” radeon power_table_6 state_index/i mismatch -> heap overflow

File: sys/dev/drm/radeon/radeon_atombios.c:2697-2735 Class: memory corruption (hardware/firmware-attacker; malicious CISS PCI device or malicious VBIOS at driver attach). No local-unprivileged syscall trigger on the audit guest (no HP Smart Array / no AMD GPU present).

Reproduce

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

What the harness does

harness.c is a faithful userspace replica of the kernel parsing routine cited above, fed crafted controller/VBIOS data that the real malicious device would supply. It demonstrates the out-of-bounds access / overflow / underflow using the real kernel macros and struct sizes, with a canary or computed-index check to make the OOB observable without needing the hardware.

Expected output

A [BUG REPRODUCED] (or UNDERFLOW for DF-1199) marker plus the computed out-of-range index / overflow byte count / underflowed loop count. See run.log for the captured decisive run.

Fix

See fix.diff (git-apply-able) and VERDICT.md. The fix was validated to compile (module rebuilt with -Werror) β€” see fix_build.log. No live-kernel trigger exists on the guest, so the fix is validated at the applies + compiles + closes-the-code-path level.

Artifacts

VERDICT.md (full narrative), harness.c, build.sh, run.sh, run.log, env.txt, fix.diff, fix_build.log, manifest.json.

VERDICT.md verdict full narrative + fix (alloc at state_index, free on skip)
↓ download raw

DF-1198 β€” radeon_atombios_parse_power_table_6 state_index/i mismatch (heap overflow)

Verdict

REPRODUCED (harness) β€” real bug confirmed by source trace + userspace replica. Impact class: kernel heap overflow of power_state[].clock_info. No local-unprivileged trigger on the audit guest (no AMD GPU); trigger requires a malicious VBIOS (malicious PCIe GPU / VFIO passthrough) at driver probe. uid=0 chain N/A β€” hardware/firmware-attacker class.

Mechanism (confirmed path:line)

radeon_atombios_parse_power_table_6() (sys/dev/drm/radeon/radeon_atombios.c) loops over the VBIOS power-state array. It allocates clock_info keyed on the loop index i, but writes through a second index state_index that only advances when a state yields β‰₯1 valid clock mode:

  • sys/dev/drm/radeon/radeon_atombios.c:2711-2714 β€” rdev->pm.power_state[i].clock_info = kzalloc(sizeof(radeon_pm_clock_info) * (ucNumDPMLevels ? ucNumDPMLevels : 1), GFP_KERNEL); (allocation sized for state i),
  • sys/dev/drm/radeon/radeon_atombios.c:2722-2724 β€” radeon_atombios_parse_pplib_clock_info(rdev, state_index, mode_index, clock_info) which writes power_state[state_index].clock_info[mode_index] (radeon_atombios.c:2484 etc.),
  • sys/dev/drm/radeon/radeon_atombios.c:2729-2732 β€” the no-DPM branch also writes power_state[state_index].clock_info[0],
  • state_index increments only in the if (mode_index) branch (radeon_atombios.c:2736-2739).

When state[0] has DPM levels that all fail validation (mode_index stays 0 β†’ state_index does not advance), state[1]'s writes land in power_state[state_index==0].clock_info β€” a buffer sized for state[0]'s single DPM level. State[1] writing N valid modes overflows it by (N βˆ’ state[0].alloc) * sizeof(struct radeon_pm_clock_info) bytes into the adjacent heap. The allocation (keyed on i) and the writes (keyed on state_index) diverge precisely when a prior state stalls state_index.

Harness proof (run.log)

harness.c replicates the loop with VBIOS state[0]={1 DPM level, invalid} and state[1]={4 DPM levels, all valid}, with a canary slot after state[0]'s 1-element allocation:

  state i=1 writes via state_index=0 (alloc for state[0]=1 entries)
  [!] OOB WRITE clock_info[1] into buffer sized 1 (state_index=0)
  state i=1 writes via state_index=0 (alloc for state[0]=1 entries)
  [!] OOB WRITE clock_info[2] into buffer sized 1 (state_index=0)
  state i=1 writes via state_index=0 (alloc for state[0]=1 entries)
  [!] OOB WRITE clock_info[3] into buffer sized 1 (state_index=0)
[BUG REPRODUCED] 3 OOB clock_info writes, victim canary clobbered 48 bytes

Why not a live-kernel trigger / no uid0 chain

radeon (/boot/kernel/radeon.ko) attaches only to AMD/ATI Radeon PCIe GPUs (none on the guest β†’ never loaded). The power table is parsed from the GPU's VBIOS (atom_parse_data_header) at probe β€” not reachable from any unprivileged syscall. A malicious VBIOS via VFIO passthrough or a malicious physical GPU is the realistic threat. Hardware/firmware-attacker class; no local-privesc chain.

Fix (fix.diff)

Make allocation and writes use the same index (state_index): allocate power_state[state_index].clock_info (instead of [i]), and when a state yields zero valid modes, free its allocation so the state_index slot is reused by the next iteration (no leak, no divergence). This preserves the compaction semantics while closing the overflow. Supersedes the finding's "use i instead of state_index" proposal β€” that alternative would break the state_index-based compaction the rest of the function relies on (post-loop iteration at radeon_atombios.c:2744 and the returned state_index).

Fix validation

radeon.ko rebuilt from patched source (DF-1198/1199 applied) compiled with -Werror and linked (/usr/obj/usr/src/sys/dev/drm/radeon/radeon.ko, 2030904 B; radeon_atombios.o rebuilt, 48448 B). fix_status: not_testable (no AMD GPU for a live trigger; validated applies + compiles + closes the path).

Fix verification

not_testable

compile+harness validated

module build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

REPRODUCED (harness). radeon power_table_6 alloc by i but write by state_index -> clock_info OOB. radeon not in GENERIC.