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

Heap OOB write in ci_setup_default_dpm_tables from unbounded VBIOS dependency-table counts

Summary

ci_setup_default_dpm_tables at ci_dpm.c:3514-3566: sclk/mclk/vddc/vddci/mvdd loops iterate VBIOS-supplied count (u8 up to 255) writing into dpm_levels[MAX_REGULAR_DPM_NUMBER=8]. Only lower-bound checked. Crafted VBIOS with count>8 overflows sclk_table into mclk_table/pcee/vddc/vddci/mvdd then ci_power_info pointer fields. Vddc loop also OOB-reads std_voltage_table->entries[i]. Same class as DF-1141 (amdgpu). Fix: upper-bound all counts against MAX_REGULAR_DPM_NUMBER.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1150 Β· 11 files
FileTypeDescriptionSize
harness.c trigger-source userspace replica of the 5 ci_setup_default_dpm_tables loops; counts OOB writes for crafted VBIOS count=255 4.8 KB view raw
fix.diff suggested-fix git-apply-able: clamp each loop to MAX_REGULAR_DPM_NUMBER + clamp .count (6 clamps) 2.8 KB view raw
build.sh build-script cc -O2 -Wall -o harness harness.c 486 B view raw
run.sh run-script ./harness 214 B view raw
build.log build-log radeon.ko module build with fix applied (ci_dpm.o, -Werror, rc=0) 1.2 KB view raw
run.log run-log decisive harness run: 1235 OOB writes confirmed, fix -> 0 726 B view raw
env.txt environment guest uname/cc, no AMD GPU, radeon not in GENERIC 1.3 KB view raw
VERDICT.md verdict full narrative: mechanism, harness, threat model, uid0 assessment, fix validation 5.3 KB ↓ raw
README.md readme how to reproduce + status 1.1 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 how to reproduce + status
↓ download raw

DF-1150 β€” PoC evidence pack

Heap OOB write in ci_setup_default_dpm_tables (radeon sys/dev/drm/radeon/ci_dpm.c:3514-3565).

ci_setup_default_dpm_tables fills five DPM tables (dpm_levels[MAX_REGULAR_DPM_NUMBER=8]) from VBIOS dependency-table counts that are u8 (up to 255) and only lower-bound checked. A crafted VBIOS (count>8) overflows each 8-entry dpm_levels[] into the adjacent ci_single_dpm_table and past struct ci_dpm_table.

Status

Source-confirmed + harness. Latent at runtime on this guest: radeon is optional radeon drm, NOT in X86_64_GENERIC, and the guest has no AMD GPU.

Reproduce

./build.sh          # cc -O2 -Wall -o harness harness.c   (unprivileged maxx)
./run.sh            # prints the OOB write count (1235 for count=255) + fix check

Expected (harness): DF-1150: CONFIRMED OOB heap write ... (VBIOS count=255 > MAX_REGULAR_DPM_NUMBER=8).

Fix

fix.diff clamps every loop to MAX_REGULAR_DPM_NUMBER (and clamps .count). Validated: applies + compiles clean (full radeon.ko build, ci_dpm.o, -Werror). See VERDICT.md for the full analysis.

VERDICT.md verdict full narrative: mechanism, harness, threat model, uid0 assessment, fix validation
↓ download raw

DF-1150 β€” Heap OOB write in ci_setup_default_dpm_tables (radeon ci_dpm.c)

Verdict

REPRODUCED (code-level / harness), latent at runtime on this guest. The bug is a genuine unbounded-copy-into-fixed-array heap OOB write, confirmed by line-by-line source trace and a userspace harness that reproduces the exact indexing. It is not triggerable at runtime on this guest (radeon is optional radeon drm, NOT in X86_64_GENERIC, and the guest has no AMD GPU β€” only QEMU std VGA 0x1234:0x1111). This is the valid hard blocker: runtime-unreachable on this guest (no matching hardware) β€” a latent HW-gated write. No exploit.c was written: this is not a userspace-reachable primitive (no unprivileged guest syscall can inject a VBIOS).

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/radeon/ci_dpm.h:59,64). ci_dpm_table holds five such tables (sclk/mclk/vddc/vddci/mvdd).
  • Attacker-controlled count: the per-table entry counts come straight from the VBIOS dependency tables (allowed_sclk_vddc_table->count, allowed_mclk_table->count) β€” u8 up to 255 β€” stored unclamped.
  • Unbounded loops into fixed arrays in ci_setup_default_dpm_tables (sys/dev/drm/radeon/ci_dpm.c):
  • sclk dedup loop :3514 writes dpm_levels[count], count++ per distinct entry β†’ up to 255 writes into [8].
  • mclk dedup loop :3527 (same shape).
  • vddc loop :3539 writes vddc_table.dpm_levels[i] for i in [0, count) (direct index), and OOB-reads std_voltage_table->entries[i]; .count set to the unbounded count :3546.
  • vddci loop :3550 + .count :3555; mvdd loop :3560 + .count :3565 (identical, unbounded).
  • Effect: with a crafted VBIOS (count>8) the writes run off each 8-entry dpm_levels[] into the next ci_single_dpm_table and past struct ci_dpm_table into adjacent ci_power_info heap fields.
  • The ci_reset_single_dpm_table helper (:3408) only zeros dpm_levels[0..7]; it does not bound the subsequent fill loops.

Evidence (harness)

harness.c (run as unprivileged maxx) models all five loops with a crafted VBIOS count of 255. Result: 1235 writes land at dpm_levels[8..] across the five loops. With the fix (&& i < MAX_REGULAR_DPM_NUMBER), the OOB count drops to 0. (Full output in run.log.)

Threat model / reachability

  • Attacker: malicious/reflashed VBIOS, or a malicious PCIe/Thunderbolt AMD Sea Islands GPU whose VBIOS dependency tables ship count > 8. Reached at DPM init (ci_dpm_enable β†’ ci_setup_default_dpm_tables).
  • On this guest: NOT reachable. radeon is not in GENERIC; no AMD GPU. The realistic impact ceiling (on physical AMD CIK hardware) is kernel heap corruption controllable via crafted VBIOS β†’ kernel panic / DoS; uid0 escalation would require a separate slab-layout-control primitive.

Exploit chain

None developed — valid hard blocker: the primitive is only reachable on physical AMD Sea Islands hardware (or VFIO passthrough) 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.

PoC changes

Authored from scratch (the dir was empty). Deliverables: harness.c, fix.diff (clamp each loop to MAX_REGULAR_DPM_NUMBER + clamp .count), build.sh, run.sh, VERDICT.md, manifest.json, env.txt, build.log, run.log.

fix.diff adds && i < MAX_REGULAR_DPM_NUMBER to each of the five loop conditions (:3514/:3527/:3539/:3550/:3560) and clamps the three trailing .count assignments (:3546/:3555/:3565) to at most MAX_REGULAR_DPM_NUMBER. This matches the finding proposal ("upper-bound all counts against MAX_REGULAR_DPM_NUMBER"). Validated: applies + compiles clean (full radeon.ko module build, ci_dpm.o linked, -Werror) β€” see build.log.

Fix validation (Phase 8)

  • fix.diff applies cleanly (git apply --check OK; patch -p1 in-guest OK, 6 clamps).
  • Full radeon.ko module build (which generates the forwarder dir + compiles ci_dpm.o): cd /usr/src/sys/dev/drm/radeon && make β†’ rc=0, radeon.ko (2029192 B) produced, ci_dpm.o compiled & linked, 0 errors under -Werror.
  • fix_status: not_testable for runtime: radeon is not in GENERIC and there is no AMD GPU on the guest, so the live trigger cannot run. The fix is validated at apply + compile level: with the clamps, every loop iterates at most i < MAX_REGULAR_DPM_NUMBER and .count is capped, so no write reaches dpm_levels[8]. On physical CIK hardware the patched module would no longer overflow.

Kernel references (confirmed during verification)

Fix verification

not_testable

compile+harness validated

see evidence pack

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed+harness. radeon ci_setup_default_dpm_tables 5 loops unbounded vs MAX_REGULAR_DPM_NUMBER=8 -> 1235 OOB. Not in GENERIC.