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

Missing bounds check on vce_states[].clk_idx causes OOB read past ClockInfoArray

Summary

trinity_parse_power_table VCE loop at trinity_dpm.c:1809-1813: clock_array_index=vce_states[i].clk_idx (6-bit 0-63 from VBIOS). clockInfo[idx*ucEntrySize] with NO check vs ucNumEntries. idx=63 vs ucNumEntries=1 -> 1008B OOB read. Sibling of DF-1269/1307. Crafted VBIOS. Fix: check clock_array_index<ucNumEntries.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1402 Β· 10 files
FileTypeDescriptionSize
trinity_vce_oob.c trigger-source byte-exact harness replicating VCE clk_idx OOB read (clockInfo byte array) 4.0 KB view raw
build.sh build-script cc -O2 -o trinity_vce_oob trinity_vce_oob.c 90 B view raw
run.sh run-script run harness 28 B view raw
run.log run-log decisive harness output, OOB READ CONFIRMED (1008B OOB) 760 B view raw
env.txt environment uname, cc version 227 B view raw
fix.diff suggested-fix add clk_idx >= ucNumEntries guard -> continue 591 B view raw
VERDICT.md verdict full analysis (read-only primitive, no escalation) 4.0 KB ↓ raw
README.md readme reproduce guide 1.6 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 reproduce guide
↓ download raw

DF-1402 β€” trinity_parse_power_table VCE clk_idx OOB read (PoC)

Summary

The VCE power-state loop in trinity_parse_power_table (sys/dev/drm/radeon/trinity_dpm.c:1816-1823) indexes the clock info array with a VBIOS-supplied 6-bit clk_idx (0..63) and no bounds check. With ucNumEntries=1 and clk_idx=63 that is a ~1008-byte OOB read. Read-only primitive β†’ no escalation; ceiling = wrong VCE engine clock / OOB info read (DoS).

Reachability

radeon is a loadable module (radeon.ko), not in GENERIC, attaching only to AMD/ATI Radeon GPUs. The QEMU guest has no AMD GPU, so the bug is not live-reachable here. Proven deterministically with a byte-exact harness replicating struct _ClockInfoArray (pptable.h:446-454), where clockInfo is a byte array so [idx*ucEntrySize] is a byte offset.

Build / run

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

Expected output (bug present)

  level 0: clk_idx=63 -> byte offset [63*16=1008] OOB -> sclk=0xdddddd
guard present? : NO (clock path at :1789 checks, this VCE loop does not)
OOB READ CONFIRMED: clk_idx=63 vs ucNumEntries=1 reads ~1008 bytes OOB per level (idx*ucEntrySize).

On a fixed driver the guard continues on clk_idx >= ucNumEntries.

Fix

Add the missing guard mirroring the clock path: if clock_array_index >= clock_info_array->ucNumEntries, continue (fix.diff). Validated to compile into a rebuilt radeon.ko (-Werror, rc=0; trinity_dpm.o built).

Files

  • trinity_vce_oob.c β€” byte-exact harness.
  • build.sh / run.sh / run.log / env.txt / fix.diff / VERDICT.md / manifest.json.
VERDICT.md verdict full analysis (read-only primitive, no escalation)
↓ download raw

DF-1402 β€” trinity_parse_power_table VCE clk_idx OOB read

Verdict: REPRODUCED (primitive proven via source trace + byte-exact harness). Fix compiles.

In the VCE power-state loop of trinity_parse_power_table, a VBIOS-supplied 6-bit clk_idx (0..63) indexes the clock info array with no bounds check β€” matching exactly the finding's claim of ~1008-byte OOB read. Read-only primitive (no write-through), so no escalation chain; ceiling = OOB-info read / wrong VCE clock (DoS). Confirmed real by source tracing + harness. Not live-reachable on the QEMU guest (no AMD Trinity GPU); the fix compiles.

Mechanism (trigger β†’ primitive β†’ effect)

sys/dev/drm/radeon/trinity_dpm.c:1816-1823:

/* fill in the vce power states */
for (i = 0; i < RADEON_MAX_VCE_LEVELS; i++) {
    u32 sclk;
    clock_array_index = rdev->pm.dpm.vce_states[i].clk_idx;   /* 6-bit 0..63, NO CHECK */
    clock_info = (union pplib_clock_info *)
        &clock_info_array->clockInfo[clock_array_index * clock_info_array->ucEntrySize];  /* OOB */
    sclk = le16_to_cpu(clock_info->sumo.usEngineClockLow);
    sclk |= clock_info->sumo.ucEngineClockHigh << 16;
    rdev->pm.dpm.vce_states[i].sclk = sclk;                  /* stored OOB-read value */
    ...
}

The array type (sys/dev/drm/radeon/pptable.h:446-454):

typedef struct _ClockInfoArray {
    UCHAR ucNumEntries;
    UCHAR ucEntrySize;
    UCHAR clockInfo[1];        /* BYTE array (UCHAR) */
} ClockInfoArray;

Because clockInfo is a byte array, [clock_array_index * ucEntrySize] is a byte offset β€” with clk_idx=63 and ucEntrySize=16, that is byte offset 1008; against ucNumEntries=1 (16 bytes) it reads ~992 bytes OOB. The read value is stored into vce_states[i].sclk.

The clock path at :1786-1790 guards clock_array_index >= ucNumEntries, but this VCE loop does not β€” an oversight sibling of DF-1269/1307.

Primitive

  • Class: out-of-bounds READ, VBIOS-controlled 6-bit index (0..63) vs ucNumEntries; up to ~1008 bytes OOB per the finding.
  • READ-ONLY: no write-through β†’ NOT a write primitive. Valid hard blocker for escalation (Phase 6: read-only β†’ no chain). Impact ceiling = wrong VCE engine clock / OOB info read β†’ DoS via bogus clock programming.

Reachability / threat model

  • radeon is a loadable module (radeon.ko), NOT in X86_64_GENERIC. Attaches to AMD/ATI Radeon GPUs. No AMD GPU on the QEMU guest β†’ not live-reachable here.
  • Threat: crafted VBIOS parsed at GPU attach (malicious firmware flash / GPU passthrough). Local, already-on-the-box attacker presenting a crafted VBIOS.

Harness proof (run.log)

clockInfoArray.ucNumEntries = 1, ucEntrySize = 16
  level 0: clk_idx=63 -> byte offset [63*16=1008] OOB -> sclk=0xdddddd
  ...
guard present? : NO (clock path at :1789 checks, this VCE loop does not)
OOB READ CONFIRMED: clk_idx=63 vs ucNumEntries=1 reads ~1008 bytes OOB per level (idx*ucEntrySize).

The harness initially segfaulted because clockInfo was wrongly modelled as an element array; the kernel pptable.h shows it is a UCHAR[] byte array, so the index is a byte offset β€” exactly the 1008-byte OOB the finding describes.

Fix validation

fix.diff adds the missing guard mirroring the clock path: after reading clk_idx, if clock_array_index >= clock_info_array->ucNumEntries, continue (skip that VCE level β€” no allocation in the loop, so continue is clean, and it matches the :1789 continue style). The fix was applied to in-guest /usr/src and radeon.ko rebuilt cleanly (cc ... -Werror, RC=0; trinity_dpm.o built with the fix). Runtime re-test not possible (no AMD GPU), so fix_status = not_testable for runtime, with the diff verified to apply + compile and the harness logic confirming the guard rejects the OOB index.

Kernel references

Fix verification

not_testable

compile+harness validated

module build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

REPRODUCED (harness). trinity VCE clk_idx no bounds -> 1008B OOB read. radeon not in GENERIC. Read-only.