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

Missing bounds check on nonClockInfoIndex causes OOB read past NonClockInfoArray

Summary

trinity_parse_power_table at trinity_dpm.c:1774-1776: non_clock_array_index=power_state->v2.nonClockInfoIndex (u8 from VBIOS) used without check vs ucNumEntries. Clock path at :1789 has guard, non-clock does not. Sibling of DF-1269/1333. Crafted VBIOS. Fix: check non_clock_array_index<ucNumEntries.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1401 Β· 10 files
FileTypeDescriptionSize
trinity_nonclock_oob.c trigger-source byte-exact harness replicating nonClockInfoIndex OOB read 4.1 KB view raw
build.sh build-script cc -O2 -o trinity_nonclock_oob trinity_nonclock_oob.c 105 B view raw
run.sh run-script run harness 33 B view raw
run.log run-log decisive harness output, OOB READ CONFIRMED 716 B view raw
env.txt environment uname, cc version 227 B view raw
fix.diff suggested-fix add nonClockInfoIndex >= ucNumEntries guard -> return -EINVAL 803 B view raw
VERDICT.md verdict full analysis (read-only primitive, no escalation) 4.1 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-1401 β€” trinity_parse_power_table nonClockInfoIndex OOB read (PoC)

Summary

trinity_parse_power_table (sys/dev/drm/radeon/trinity_dpm.c:1774-1776) indexes the non-clock info array with a VBIOS-supplied nonClockInfoIndex (u8) and no bounds check, unlike the clock path at :1786-1790 which IS guarded. Crafted VBIOS β†’ OOB read. Read-only primitive (no write-through) β†’ no escalation; ceiling = OOB-info read / wrong DPM settings (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 _NonClockInfoArray (pptable.h:456-464).

Build / run

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

Expected output (bug present)

nonClockInfoIndex (from VBIOS) = 200
guard present? : NO (contrast clock path at :1789 which checks >= ucNumEntries)
reads entry [200] of a 1-entry array -> OUT OF BOUNDS
OOB READ CONFIRMED: ... reads ~3200 bytes OOB ...

On a fixed driver the guard rejects indices >= ucNumEntries and returns -EINVAL.

Fix

Add the missing guard mirroring the clock path: if non_clock_array_index >= non_clock_info_array->ucNumEntries, free rdev->pm.dpm.ps, NULL it, return -EINVAL (fix.diff). Validated to compile into a rebuilt radeon.ko (-Werror, rc=0; trinity_dpm.o built).

Files

  • trinity_nonclock_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-1401 β€” trinity_parse_power_table nonClockInfoIndex OOB read

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

trinity_parse_power_table indexes the non-clock info array with a VBIOS-supplied nonClockInfoIndex (u8) and no bounds check, unlike the clock path in the same function which IS guarded. A crafted VBIOS with nonClockInfoIndex >= ucNumEntries reads out of bounds. This is a read-only primitive (the OOB data is read and stored, not written through), so there is no escalation chain β€” the impact ceiling is OOB-info read / wrong DPM settings (DoS via bogus clock programming). 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:1774-1776:

non_clock_array_index = power_state->v2.nonClockInfoIndex;   /* u8 from VBIOS, NO CHECK */
non_clock_info = (struct _ATOM_PPLIB_NONCLOCK_INFO *)
    &non_clock_info_array->nonClockInfo[non_clock_array_index];   /* OOB READ */

Contrast the clock path at :1786-1790, which IS guarded:

idx = (u8 *)&power_state->v2.clockInfoIndex[0];
for (j = 0; j < power_state->v2.ucNumDPMLevels; j++) {
    clock_array_index = idx[j];
    if (clock_array_index >= clock_info_array->ucNumEntries)   /* <-- GUARDED */
        continue;
    ...
}

The non-clock index has no equivalent check β€” an oversight sibling of DF-1269/1333.

The array type (sys/dev/drm/radeon/pptable.h:456-464):

typedef struct _NonClockInfoArray {
    UCHAR ucNumEntries;
    UCHAR ucEntrySize;
    ATOM_PPLIB_NONCLOCK_INFO nonClockInfo[1];   /* array of structs; index is element index */
} NonClockInfoArray;

non_clock_info is then passed to trinity_parse_pplib_non_clock_info(...) and its fields are READ into the power-state struct.

Primitive

  • Class: out-of-bounds READ, VBIOS-controlled index (u8, 0..255) vs ucNumEntries; with ucNumEntries=1 and index 255, reads ~255 entries past the array β†’ up to ~255*ucEntrySize bytes OOB.
  • READ-ONLY: no write-through, so this is NOT a memory-corruption write primitive. Valid hard blocker for escalation (Phase 6: read-only primitive β†’ no chain). Impact ceiling = OOB-info read / wrong DPM settings β†’ DoS via bogus clock programming on crafted VBIOS.

Reachability / threat model

  • radeon is a loadable module (radeon.ko), NOT in X86_64_GENERIC. It attaches to AMD/ATI Radeon GPUs. The QEMU guest has no AMD GPU, so trinity_parse_power_table never runs live here.
  • Threat: a crafted VBIOS (e.g. flashed malicious firmware, or a malicious virtual-GPU passthrough) parsed at GPU attach. This is a local, already-on-the-box attacker with the ability to present a crafted VBIOS image to the driver.

Harness proof (run.log)

nonClockInfoArray.ucNumEntries = 1
nonClockInfoIndex (from VBIOS) = 200
guard present? : NO (contrast clock path at :1789 which checks >= ucNumEntries)
reads entry [200] of a 1-entry array -> OUT OF BOUNDS
OOB READ CONFIRMED: nonClockInfoIndex=200 used against ucNumEntries=1 with no bounds check; reads ~3200 bytes OOB.

Fix validation

fix.diff adds the missing guard mirroring the clock path: after reading nonClockInfoIndex, if it >= non_clock_info_array->ucNumEntries, free rdev->pm.dpm.ps, set it NULL, and return -EINVAL (matching the existing error-cleanup pattern at :1780-1783 which frees dpm.ps and returns on allocation failure). The fix was applied to in-guest /usr/src and the radeon.ko module rebuilt cleanly (cc ... -Werror, RC=0; trinity_dpm.o built with the fix). Runtime re-test is not possible on this guest (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 nonClockInfoIndex no bounds -> OOB read into power state. radeon not in GENERIC. Read-only.