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

V1-branch state-array off-by-one: entry_index == ucNumStates passes 'entry_index > ucNumStates' guard

Summary

The legacy branch uses '>' so entry_index == ucNumStates passes; the read computes the state entry one ATOM_PPLIB_STATE past the array and reads ucClockStateIndices from it. Twin of DF-1472's V2 site but on the V1 path - a distinct site DF-1472's fix would not touch. >= fix (included in the DF-4247 central diff).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4248 Β· 8 files
FileTypeDescriptionSize
README.md β€” 1.6 KB ↓ raw
VERDICT.md β€” 1.7 KB ↓ raw
build.sh β€” 65 B view raw
env.txt β€” 588 B view raw
run.log β€” 425 B view raw
run.sh β€” 89 B view raw
trigger.sh β€” 472 B view raw
verdict.json β€” 1.8 KB view raw

DF-4248 β€” V1-branch state-array off-by-one (entry_index > ucNumStates)

Where

sys/dev/drm/amd/powerplay/hwmgr/processpptables.c:937 (V1 branch of pp_tables_get_entry, tables with ucTableFormatRevision < 6), twin of the known V2 off-by-one covered by DF-1472 at :913 (this site was not in DF-1472's listed scope).

What

if (entry_index > powerplay_table->ucNumStates)   /* off by one: == passes */
        return -1;
pstate_entry = (ATOM_PPLIB_STATE *)...usStateArrayOffset +
                entry_index * powerplay_table->ucStateEntrySize;

entry_index == ucNumStates walks one ATOM_PPLIB_STATE past the array and then iterates ucStateEntrySize-1 clock indices read from out-of-bounds bytes, feeding garbage ucClockStateIndices[] to the callback. Reads only (sink indices are re-derived), so impact = OOB read of the VBIOS heap image.

Trigger

Same as the VBIOS/pp_table family: crafted PowerPlay table (format rev < 6) on smu7/smu8-class hardware, or root pp_table sysfs swap racing a state enumeration between pp_tables_get_num_of_entries and pp_tables_get_entry (the two calls are separately locked in amd_powerplay.c, so a table swap in between makes entry_index == ucNumStates reachable).

Fix

Included in DF-4247's central fix.diff (> -> >=); minimal hunk:

-       if (entry_index > powerplay_table->ucNumStates)
+       if (entry_index >= powerplay_table->ucNumStates)
            return -1;

Build/run

Hardware-gated (needs an AMD pre-Vega ASIC attached); see env.txt. ./run.sh documents the absence of AMD hardware on the audit guest.

VERDICT.md
↓ download raw

DF-4248 VERDICT

status: untested (hardware-gated: no AMD GPU on the audit guest β€” run.log) impact: none demonstrated (OOB read, Low) | confidence: certain (code-trace)

Trace

processpptables.c:937 uses > where >= is required, so entry_index == ucNumStates passes the guard; :940-942 then computes the state entry at ucNumEntries * ucStateEntrySize β€” one entry past the last β€” and :953-957 loops ucStateEntrySize-1 times reading pstate_entry->ucClockStateIndices[i] from that OOB entry, with each index used to derive a pclock_info pointer handed to the hw callback (:959). Identical defect class to the V2 twin at :913 (DF-1472), but on the ucTableFormatRevision < 6 code path β€” a distinct site requiring its own hunk, which DF-1472's fix would not touch.

Normally entry_index < num_of_entries (callers iterate pp_tables_get_num_of_entries), so the off-by-one requires a table swap between the two calls (root pp_table write, amd_powerplay.c:672-706) or a malicious VBIOS that reports different counts across reads. Impact ceiling: heap OOB read of the (attacker-supplied) table image β€” low.

Fix

> -> >= at :937 (and the V2 twin at :913); both contained in DF-4247/fix.diff. fix_status: not_testable (no hardware to build/run a kernel with the fix; the one-line change is compile-trivial).

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

not_testable: hardware-gated; one-token fix included in central diff.

'>变成>=' hunk present in DF-4247/fix.diff.
per-fix-DF-4248

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md trace; fix hunk contained in DF-4247/fix.diff; run.log hardware probe.

PoC changes

n/a - pass-2 finding; trigger documentation written fresh (hardware-gated, no seed PoC existed)

Verified recommended fix

entry_index >= ucNumStates at :937

Verdict

V1-branch twin of DF-1472: pp_tables_get_entry guards the legacy (ucTableFormatRevision<6) walk with 'entry_index > ucNumStates' at :937, passing entry_index==ucNumStates; :940-957 then reads a state entry one past the array and iterates ucStateEntrySize-1 clock indices from OOB bytes. Reachable via pp_table swap between num_of_entries and get_entry calls or crafted V1 VBIOS. OOB read, Low.