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)
PoC verification
Evidence pack
findings/poc/DF-4248 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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_testablenot_testable: hardware-gated; one-token fix included in central diff.
'>εζ>=' hunk present in DF-4247/fix.diff.
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.
No comments yet.