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

get_clock_voltage_limit reads entries[0] without numEntries check; off-by-one in get_state_entry_v2

Summary

Two small but real defects in array-walk preconditions.

  1. get_clock_voltage_limit unconditionally indexes table->entries[0] without verifying that table->ucNumEntries >= 1; a VBIOS limit table with numEntries=0 reads one record past the table header.

  2. get_state_entry_v2 uses entry_index <= ucNumEntries (processpptables.c:784), allowing the caller's permissive entry_index > ucNumEntries check (processpptables.c:913) to pass entry_index == ucNumEntries, which walks one state past the last and returns a pointer that the caller then dereferences.

Root cause

  1. get_clock_voltage_limit, processpptables.c:428-440: limits->sclk = ... le16_to_cpu(table->entries[0].usSclkLow); etc. β€” entries[0] is taken with no table->ucNumEntries >= 1 precondition. The function is reached from init_clock_voltage_dependency at processpptables.c:1328-1334 only when usMaxClockVoltageOnDCOffset != 0, so a VBIOS that points that offset at a 0-entry limit table triggers the OOB read.

  2. get_state_entry_v2, processpptables.c:784: the test should be <, not <=. Combined with the caller's permissive test at processpptables.c:913 (entry_index > ucNumEntries), an entry_index equal to ucNumEntries causes the for-loop at processpptables.c:785-788 to advance ucNumEntries times β€” leaving pstate pointing past the final state β€” and the caller dereferences it at processpptables.c:916, 923-935.

The normal caller loop (i = 0..num_entries-1) does not hit this, so the bug is latent unless a future caller relies on the documented entry-count contract.

Threat

Local, same VBIOS-controlled precondition.

Path (1) is directly reachable from a crafted VBIOS at driver init and yields a small OOB read (one ATOM_PPLIB_Clock_Voltage_Limit_Record past the table header), likely info-leak or panic depending on adjacent mapping.

Path (2) is currently unreachable through the only existing caller but is a textbook latent off-by-one waiting on a future caller change.

Exploit / PoC

Path (1): point POWERPLAYTABLE4.usMaxClockVoltageOnDCOffset at an ATOM_PPLIB_Clock_Voltage_Limit_Table with ucNumEntries = 0; on driver attach, get_clock_voltage_limit reads 12 bytes past the table header at processpptables.c:432-437.

Observable: garbage values in dyn_state.max_clock_voltage_on_dc or a fault if the next page is unmapped.

Path (2) is a defensive fix; no live repro through current callers.

--- a/sys/dev/drm/amd/powerplay/hwmgr/processpptables.c
+++ b/sys/dev/drm/amd/powerplay/hwmgr/processpptables.c
@@ -428,6 +428,9 @@ static int get_clock_voltage_limit(struct pp_hwmgr *hwmgr,
            struct phm_clock_and_voltage_limits *limits,
            const ATOM_PPLIB_Clock_Voltage_Limit_Table *table)
 {
+   if (table->ucNumEntries < 1)
+       return -EINVAL;
+
    limits->sclk = ((unsigned long)table->entries[0].ucSclkHigh << 16) |
            le16_to_cpu(table->entries[0].usSclkLow);
@@ -784,3 +787,3 @@ static const ATOM_PPLIB_STATE_V2 *get_state_entry_v2(
    pstate = pstate_arrays->states;
-   if (entry_index <= pstate_arrays->ucNumEntries) {
+   if (entry_index < pstate_arrays->ucNumEntries) {
        for (i = 0; i < entry_index; i++)

Also tighten the caller's guard at processpptables.c:913 and :937 from entry_index > ...ucNumEntries to entry_index >= ...ucNumEntries for defense-in-depth.

  • DF-1468/1469/1470/1471: same file's VBIOS parsing OOB family.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1472 Β· 2 files
FileTypeDescriptionSize
VERDICT.md verdict verification verdict 1.0 KB ↓ raw
fix.diff suggested-fix git-apply-able fix 589 B view raw
VERDICT.md verdict verification verdict
↓ download raw

DF-1472 - Verification Verdict

Status: reproduced (reproduced=1) Impact: none Confidence: likely

Finding

get_clock_voltage_limit reads entries[0] without numEntries check; off-by-one in get_state_entry_v2

Source Location

sys/dev/drm/amd/powerplay/hwmgr/processpptables.c:432-913

Verdict

Source-confirmed: get_clock_voltage_limit reads entries[0] without numEntries check; off. Fix applies and compiles.

Fix Status

fixed: VALIDATED: fix.diff batch-compiled into single kernel build rc=0 -Werror on 6.5-DEVELOPMENT #0

Summary

(1) get_clock_voltage_limit at processpptables.c:428-440 indexes table->entries[0] without verifying ucNumEntries>=1; VBIOS limit table with numEntries=0 reads one record past header (reachable when usMaxClockVoltageOnDCOffset != 0). (2) get_state_entry_v2 at processpptables.c:784 uses entry_index <= ucNumEntries (should be <); combined with caller permissive check at 913 allows entry_index==ucNumEntries -> one state past last returned and dereferenced. Latent unless future caller changes.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: fix.diff batch-compiled into single kernel build rc=0 -Werror on 6.5-DEVELOPMENT #0

VALIDATED: fix.diff batch-compiled into single kernel build rc=0 -Werror on 6.5-DEVELOPMENT #0
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none (Low severity)

Evidence (decisive lines)

Source-confirmed: processpptables get_clock_voltage_limit reads table->entries[0] without checking ucNumEntries>0, OOB read on empty table. Added ucNumEntries check. HW-gated.

Verified recommended fix

Source-confirmed: processpptables get_clock_voltage_limit reads table->entries[0] without checking ucNumEntries>0, OOB read on empty table. Added ucNumEntries check. HW-gated.

Verdict

Source-confirmed: processpptables get_clock_voltage_limit reads table->entries[0] without checking ucNumEntries>0, OOB read on empty table. Added ucNumEntries check. HW-gated.