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

Heap OOB write in ci_setup_default_dpm_tables: VBIOS counts index fixed-size dpm_levels[8] unchecked

Summary

ci_setup_default_dpm_tables at ci_dpm.c:3593-3673: sclk/mclk/vddc/vddci/mvdd loops iterate to VBIOS-supplied count (u8, up to 255) writing into fixed-size dpm_levels[MAX_REGULAR_DPM_NUMBER=8]. No clamp. Crafted VBIOS with >8 entries overflows into adjacent ci_power_info fields (golden_dpm_table, voltage_control, SMC offsets). Corrupted counts then drive secondary OOB in ci_populate_all_graphic/memory_levels over GraphicsLevel[8]/MemoryLevel[6]. Attacker: malicious VBIOS via reflash/VFIO/QEMU. Fix: clamp each loop to MAX_REGULAR_DPM_NUMBER.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1141 Β· 9 files
FileTypeDescriptionSize
fix.diff suggested-fix clamp dpm-table loops to MAX_REGULAR_DPM_NUMBER (git-apply-able) 2.7 KB view raw
build.sh build-script apply fix.diff + incremental compile of amdgpu ci_dpm.o 879 B view raw
run.sh run-script documents runtime-unreachable (no AMD GPU on guest) 1.2 KB view raw
VERDICT.md verdict full source-trace + mechanism + fix rationale 4.3 KB ↓ raw
env.txt environment guest uname, pci, driver build status 2.0 KB view raw
build.log build-log fix compile-validation output (patched ci_dpm.o, -Werror clean) 1.5 KB view raw
README.md readme human repro summary 1.9 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 repro summary
↓ download raw

DF-1141 β€” Heap OOB write in ci_setup_default_dpm_tables (amdgpu ci_dpm.c)

Status: REPRODUCED at code level β€” latent at runtime on this guest (no AMD GPU). Severity (finding): High Β· CWE-787 Out-of-bounds Write

What the bug is

ci_setup_default_dpm_tables() (sys/dev/drm/amd/amdgpu/ci_dpm.c:3582) copies VBIOS-supplied PowerPlay dependency-table counts (a u8, up to 255, from amdgpu_parse_clk_voltage_dep_table at amdgpu_dpm.c:302) into the fixed dpm_levels[MAX_REGULAR_DPM_NUMBER=8] arrays (ci_dpm.h:60,65) with no clamp. A crafted VBIOS with ucNumEntries > 8 overflows sclk/mclk/vddc/ vddci/mvdd tables into adjacent ci_power_info fields.

Why it does not trigger here

The QEMU/KVM guest has no AMD GPU (pciconf shows only 0x1234:0x1111 QEMU std VGA), and amdgpu is not in X86_64_GENERIC. The path runs only at DPM init on real Sea Islands (Bonaire/Hawaii) hardware with a malicious VBIOS. This is a latent finding on the audit guest.

What was validated

  1. Source trace β€” the unbounded loops and the unbounded count source are confirmed (see VERDICT.md kernel_refs).
  2. Baseline compiles β€” amdgpu.ko (incl. ci_dpm.o) builds clean from /usr/src under -Werror.
  3. Fix compiles β€” fix.diff applies (3 hunks) and the patched ci_dpm.o rebuilds clean under -Werror (ci_dpm.o: 68080 -> 68176 bytes).

Reproduce (compile-validation only β€” no runtime trigger on this guest)

# on the guest, as root, from the with-src snapshot:
scp this-folder/fix.diff root@guest:/root/df1141.diff
cd /usr/src && patch -p1 --forward < /root/df1141.diff
cd sys/dev/drm/amd/amdgpu && rm -f ci_dpm.o && make ci_dpm.o   # -Werror clean

Or just ./build.sh after copying fix.diff to /root/.

Fix

fix.diff clamps each of the five loops in ci_setup_default_dpm_tables to MAX_REGULAR_DPM_NUMBER and clamps the trailing .count assignments β€” matches the finding's recommendation.

VERDICT.md verdict full source-trace + mechanism + fix rationale
↓ download raw

DF-1141 β€” Heap OOB write in ci_setup_default_dpm_tables (amdgpu ci_dpm.c)

Verdict

REPRODUCED (code-level, latent at runtime). The bug is a genuine unbounded-copy-into-fixed-array heap OOB write, confirmed by source trace. It is not triggerable at runtime on this guest (no AMD GPU; amdgpu is not in GENERIC), so runtime impact is not_testable here. The fix.diff was validated to apply + compile cleanly under -Werror.

Mechanism (trigger -> primitive -> effect)

  • Source array: struct ci_single_dpm_table { u32 count; struct ci_dpm_level dpm_levels[MAX_REGULAR_DPM_NUMBER]; } with MAX_REGULAR_DPM_NUMBER = 8 (sys/dev/drm/amd/amdgpu/ci_dpm.h:60,65). ci_dpm_table holds five such tables (sclk/mclk/vddc/vddci/mvdd) back-to-back (ci_dpm.h:68-75).
  • Attacker-controlled count: the per-table entry counts come straight from VBIOS PowerPlay ATOM_PPLIB_Clock_Voltage_Dependency_Table.ucNumEntries (a u8, i.e. up to 255) via amdgpu_parse_clk_voltage_dep_table (sys/dev/drm/amd/amdgpu/amdgpu_dpm.c:282-304), which stores atom_table->ucNumEntries into amdgpu_table->count with no upper bound (amdgpu_dpm.c:302).
  • Unbounded loops into fixed arrays in ci_setup_default_dpm_tables (sys/dev/drm/amd/amdgpu/ci_dpm.c:3582):
  • sclk dedup loop :3621 writes dpm_levels[count] with count derived from allowed_sclk_vddc_table->count (write index count++ on each distinct entry -> up to ucNumEntries writes into [8]).
  • mclk dedup loop :3634 (same shape).
  • vddc loop :3646 writes vddc_table.dpm_levels[i] for i in [0, allowed_sclk_vddc_table->count) (index = i, unbounded), then sets vddc_table.count = allowed_sclk_vddc_table->count (:3653).
  • vddci loop :3657 + .count :3662; mvdd loop :3667 + .count :3672 (identical, unbounded).
  • Effect: with a crafted VBIOS (ucNumEntries > 8), the writes run off the end of each 8-entry dpm_levels[] into the next ci_single_dpm_table (overwriting the next table's count + dpm_levels) and ultimately past struct ci_dpm_table into adjacent ci_power_info fields (golden_dpm_table, voltage_control, SMC offsets). The corrupted counts then drive the secondary population loops in ci_populate_all_graphic/memory_levels past GraphicsLevel[8]/MemoryLevel[6].

Threat model / reachability

  • Attacker: malicious/reflashed VBIOS, or a malicious PCIe/Thunderbolt AMD GPU whose VBIOS PowerPlay tables ship ucNumEntries > 8. Reached at DPM init (ci_dpm_enable -> ci_setup_default_dpm_tables).
  • On this guest: NOT reachable. pciconf shows only QEMU std VGA (0x1234:0x1111); amdgpu is not in X86_64_GENERIC. This is the valid hard blocker: runtime-unreachable on this guest (no matching hardware) β€” a latent bug. The realistic impact ceiling (on physical AMD CIK hardware) is kernel heap corruption controllable via crafted VBIOS.

Exploit chain

None developed β€” valid hard blocker: the primitive is only reachable on physical AMD Sea Islands hardware (or VFIO-passthrough of such a GPU) with a malicious VBIOS, none of which exist on this QEMU/KVM guest. There is no unprivileged-guest syscall path to inject a VBIOS. Per the bright-line rule this is a latent HW-dependent write, not a default-GENERIC unpriv->kernel chain; the demonstrated work is the source-level confirmation + compiling fix. (No exploit.c written β€” not a userspace-reachable primitive.)

PoC changes

No trigger PoC exists (none was seeded for this latent finding). This folder adds: fix.diff (clamp each loop to MAX_REGULAR_DPM_NUMBER), build.sh (apply fix + incremental compile of ci_dpm.o), run.sh (documents the runtime-unreachable status), VERDICT.md, manifest.json, env.txt, build.log.

Clamp every loop in ci_setup_default_dpm_tables to MAX_REGULAR_DPM_NUMBER and clamp the trailing .count assignments. Implemented in fix.diff: - :3621/:3634/:3646/:3657/:3667 loop conditions gain && i < MAX_REGULAR_DPM_NUMBER; - :3653/:3662/:3672 .count set to min((u32)<src>->count, (u32)MAX_REGULAR_DPM_NUMBER).

This matches the finding proposal ("clamp each loop to MAX_REGULAR_DPM_NUMBER"). min/min_t come from the DRM linux-compat header sys/dev/drm/include/linux/kernel.h already force-included by the module.

Fix verification

not_testable

compile validated -Werror

module build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed+compile. ci_setup_default_dpm_tables 5 loops unbounded vs MAX_REGULAR_DPM_NUMBER=8. amdgpu not in GENERIC.