hwmgr->num_vce_state_tables stored unclamped (up to 255) over vce_states[6] β fill loop silently skipped, count left as a loaded gun for any consumer
Summary
The count is stored BEFORE the AMD_MAX_VCE_LEVELS guard, which only skips the fill loop - a crafted VBIOS leaves a stored count up to 255 over the 6-entry array with zero entries populated. The consumer trusts the stored count as the array bound; today coincidentally fenced by the ioctl-side constant in the only caller loop. The amdgpu DPM path clamps the same count, proving the intended invariant. Clamp-and-fill fix (case 7 of the DF-4388 unit test, PASS).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4389 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.2 KB | β raw | |
| VERDICT.md | β | 2.2 KB | β raw | |
| fix.diff | β | 1000 B | view raw | |
| build.sh | β | 65 B | view raw | |
| run.sh | β | 89 B | view raw | |
| run.log | β | 34 B | view raw | |
| env.txt | β | 478 B | view raw | |
| manifest.json | β | 735 B | view raw | |
| verdict.json | β | 2.2 KB | view raw |
DF-4389 β hwmgr->num_vce_state_tables stored unclamped (up to 255) against vce_states[6]; fill loop silently skipped
hwmgr->num_vce_state_tables = i = ppt_get_num_of_vce_state_table_entries_v1_0(hwmgr);
if ((i != 0) && (i <= AMD_MAX_VCE_LEVELS)) { /* guard covers FILL only */
for (j = 0; j < i; j++)
ppt_get_vce_state_table_entry_v1_0(hwmgr, j, &(hwmgr->vce_states[j]), NULL, &flags);
}
icomes straight from the VBIOS VCE-state-tableucNumEntries(UCHAR, 0..255 β and whenusVCEStateTableOffset==0, from whatever byte the PowerPlay header alias yields; DF-1576 notes the missing zero-check).hwmgr->vce_states[AMD_MAX_VCE_LEVELS]= 6 entries (sys/dev/drm/amd/powerplay/inc/hwmgr.h:723,sys/dev/drm/amd/include/kgd_pp_interface.h:74).- The count is stored unconditionally; a VBIOS with
ucNumEntries > 6leavesnum_vce_state_tablesup to 255 while the array holds at most 6 (zeroed or stale) entries. - Consumer:
pp_dpm_get_vce_clock_state(sys/dev/drm/amd/powerplay/amd_powerplay.c:868-869):if (idx < hwmgr->num_vce_state_tables) return &hwmgr->vce_states[idx];β currently reached only fromAMDGPU_INFO_VCE_CLOCK_TABLE(amdgpu_kms.c:775) whose loop is bounded byAMDGPU_VCE_CLOCK_TABLE_ENTRIES = 6(amdgpu_drm.h:962), so today the OOB read is fenced by coincidence: any future consumer trusting the stored count (or a raised ioctl bound) reads up to 249 entries pastvce_states[]insidestruct pp_hwmgrand copies the fields to userspace. - Sibling evidence the clamp is the intended semantics: the amdgpu DPM path
clamps the very same firmware count β
sys/dev/drm/amd/amdgpu/amdgpu_dpm.c:580-581:numEntries > AMD_MAX_VCE_LEVELS ? AMD_MAX_VCE_LEVELS : numEntries.
Build / run
Hardware-gated on the audit guest (no AMD GPU β run.log); the clamp
arithmetic is case 7 of ../DF-4388/v1_clamp_test.c (255 β 6, PASS on the
guest) and the fix rides in DF-4388's fix.diff hunk; a standalone minimal
fix.diff is included here.
Expected
With the fix: num_vce_state_tables never exceeds 6 and the first 6 VCE
states are populated instead of none.
DF-4389 VERDICT
Trace
ppt_get_num_of_vce_state_table_entries_v1_0(:1210-1223) returns the VCE state table'sucNumEntries(UCHAR). No offset-zero check (DF-1576), sousVCEStateTableOffset==0yields an aliased header byte.get_powerplay_table_entry_v1_0(:1328) stores that value intohwmgr->num_vce_state_tablesbefore thei <= AMD_MAX_VCE_LEVELSguard, which only protects the fill loop (:1330-1333). Result forucNumEntries > 6: stored count up to 255, zero entries populated.- Destination:
hwmgr->vce_states[AMD_MAX_VCE_LEVELS=6](hwmgr.h:723, kgd_pp_interface.h:74). - Consumer
pp_dpm_get_vce_clock_state(amd_powerplay.c:868-869) trusts the stored count as the exclusive bound forvce_states[idx].
Impact today: bounded only because the sole caller path
(AMDGPU_INFO_VCE_CLOCK_TABLE, amdgpu_kms.c:775-784) iterates
i < AMDGPU_VCE_CLOCK_TABLE_ENTRIES = 6 β a constant that happens to equal
the array size. With that coincidence the observable defect is a
misreported VCE clock table (zeros) on hostile VBIOS; without it, an OOB
read of struct pp_hwmgr tail β userspace info leak. The amdgpu DPM path
(amdgpu_dpm.c:580-581) clamps this same firmware count, showing the
intended invariant.
Verification (Phase V)
Hardware-gated (no AMD GPU on guest, run.log). Demonstrated:
- clamp arithmetic (255 β 6) is case 7 of DF-4388's unit test β PASS on the
guest (../DF-4388/unit_test_guest.log).
- fix.diff (1 hunk, standalone) git apply --check OK; identical hunk is
part of DF-4388's compile-validated process_pptables_v1_0_fixed.c
(guest cc -fsyntax-only, 0 errors).
Bottom line
Certain (code-proven), currently impact-fenced by a coincidental equal constant in the only consumer β Low (latent OOB / wrong-data, hardening). Fixed centrally in DF-4388's diff; standalone hunk provided.
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.
Fix verification
not_testablenot_testable at runtime (no AMD ASIC on guest); standalone fix.diff git-apply OK and the identical hunk is inside DF-4388's compile-validated patched file (guest cc, 0 errors).
fix.diff; ../DF-4388/fix_build.log; ../DF-4388/unit_test_guest.log
Confirmed kernel references
Detail
Evidence (decisive lines)
README.md trace (path:line); ../DF-4388/unit_test_guest.log case 7 (255->6 PASS); fix.diff git apply --check OK; integrated fixed file compile-validated in DF-4388 (0 errors)
PoC changes
n/a - pass-2 finding authored fresh
Verified recommended fix
clamp num_vce_state_tables to AMD_MAX_VCE_LEVELS before storing (fix.diff; also in DF-4388 fix.diff)
Verdict
process_pptables_v1_0.c:1328 stores the firmware VCE-state count (UCHAR, up to 255; garbage when usVCEStateTableOffset==0) into hwmgr->num_vce_state_tables BEFORE the AMD_MAX_VCE_LEVELS guard, which only skips the fill - so a crafted VBIOS leaves a count of up to 255 over a 6-entry hwmgr->vce_states[] (hwmgr.h:723). Today the sole consumer path is coincidentally fenced (AMDGPU_INFO_VCE_CLOCK_TABLE loops i<6, amdgpu_kms.c:775); any consumer trusting the stored count OOB-reads struct pp_hwmgr into userspace. amdgpu_dpm.c:580 clamps the same count, showing the intended invariant. Low/latent: clamp added in DF-4388 fix.diff; standalone hunit provided. Hardware-gated on guest.
No comments yet.