phm_find_closest_vddci has no empty-table guard β count==0 indexes entries[0xFFFFFFFF]: wild kernel read ~32 GiB past the heap object (panic)
Summary
The only family member without a count!=0 guard: with count==0 the fallback reads entries[i-1] with uint32 i==0 - byte offset 0x7FFFFFFF8, a wild read faulting on any real mapping. count==0 reachable from a crafted VBIOS (the GPIO LUT path accepts ucGpioEntryNum==0 and the SMC populators invoke the lookup for legal vddci==0 entries). Harness: safe mode prints the exact offset; deref mode SIGSEGVs the verbatim function; patched twin clean. Guard-like-siblings fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4666 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| find_closest_vddci_zero.c | β | 4.2 KB | view raw | |
| find_closest_vddci_zero_patched.c | β | 1.5 KB | view raw | |
| build.sh | β | 211 B | view raw | |
| run.sh | β | 90 B | view raw | |
| build.log | β | 0 B | β download | |
| run.log | β | 1.0 KB | view raw | |
| env.txt | β | 188 B | view raw | |
| VERDICT.md | β | 2.6 KB | β raw | |
| fix.diff | β | 772 B | view raw | |
| manifest.json | β | 1011 B | view raw | |
| verdict.json | β | 3.6 KB | view raw |
DF-4666 β VERDICT
Status: reproduced (harness: verbatim function, 1:1 struct layout; run on the DragonFly 6.5-DEVELOPMENT guest).
Observations (run.log)
- [safe] With
count = 0(the crafted-VBIOS state produced by a GPIO VDDCI LUT withucGpioEntryNum == 0), the verbatim fallback computes&entries[(uint32_t)0 - 1]β byte offset 0x7FFFFFFF8 (34,359,738,360 β 32 GiB) pastentriesβ far outside any possible mapping of thestruct smu7_hwmgrheap object. - [deref] Actually invoking the verbatim function with count==0 dies
SIGSEGV (exit 139) β the read address is unmapped. In the kernel this
is a page fault in
phm_find_closest_vddciduring SMC-table population at driver load β panic. - [normal] Control run (count=4) shows the populated path behaves correctly β the harness compiles the function faithfully.
- [patched] Twin with the
count == 0guard returns the caller's vddci with no wild access; the count=4 path is unchanged β fix validated.
Why it is reachable
atomctrl_get_voltage_table_v3 (ppatomctrl.c:546-563) accepts
ucGpioEntryNum == 0 (assert is only <= 32) and returns success with
count = 0; smu7_construct_voltage_tables (smu7_hwmgr.c:276-282) accepts
it for vddci_control == BY_GPIO; nothing downstream rejects an empty table
(the trim at smu7_hwmgr.c:337-342 only handles too-large). The polaris10 /
vegam / fiji SMC populators then call phm_find_closest_vddci whenever a dep
entry has vddci == 0 (legal VBIOS field value). Threat model: crafted VBIOS
/ VFIO / root sysfs pp_table β same as the rest of the powerplay family.
Ceiling
Wild read at a fixed huge offset β deterministic panic (DoS); no disclosure or write primitive. Severity Medium, consistent with sibling DF-1798 (Medium, OOB read via unvalidated vddInd).
Kernel references
- sys/dev/drm/amd/powerplay/hwmgr/smu_helper.c:426-437 (unguarded
entries[i-1]) - sys/dev/drm/amd/powerplay/hwmgr/smu_helper.c:393,413 (siblings that DO guard)
- sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c:546-563 (ucGpioEntryNum==0 accepted)
- sys/dev/drm/amd/powerplay/hwmgr/smu7_hwmgr.c:276-282,337-342 (count==0 survives construction)
- sys/dev/drm/amd/powerplay/smumgr/polaris10_smumgr.c:377,402 (call sites)
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
fixedPatched twin with the count==0 / >32 guard returns the caller's vddci for the empty table and still returns entries[3].value for the populated control β wild access eliminated, behavior preserved. Full kernel rebuild not performed (no AMD GPU on the audit guest; verbatim-function harness is the faithful validation surface).
findings/poc/DF-4666/run.log '[patched]' section: 'FIX VALIDATED: empty table no longer indexes entries[0xFFFFFFFF]'.
Confirmed kernel references
Detail
Exploit chain
crafted VBIOS / VFIO / root sysfs pp_table with a VDDCI GPIO LUT of zero entries (and any dep entry with vddci==0) -> vddci_voltage_table.count=0 survives construction -> phm_find_closest_vddci -> kernel read at backend+0x7FFFFFFF8 -> page fault -> panic (DoS). Read-only primitive; no disclosure or write.
Evidence (decisive lines)
findings/poc/DF-4666/run.log: '[safe] effective byte offset = 34359738360 (0x7fffffff8)' + 'WILD-READ CONFIRMED'; '[deref] ... Segmentation fault ... exit status: 139'; '[normal] returns entries[3].value = 830'; '[patched] FIX VALIDATED'.
PoC changes
Seed harness authored fresh (no prior seed): verbatim smu_helper.c:426-437 body plus 1:1 pp_atomctrl_voltage_table layout from ppatomctrl.h; three modes (safe address math, live deref, populated control) plus patched twin.
Verified recommended fix
Add the same empty-table guard as phm_get_voltage_index/phm_get_voltage_id to phm_find_closest_vddci (return caller vddci on NULL/count==0/count>32).
Verdict
Harness-verified on the DragonFly 6.5-DEVELOPMENT guest with the verbatim kernel function and 1:1 struct layout: phm_find_closest_vddci (smu_helper.c:426-437) is the only member of its lookup family with no empty-table guard; with count==0 the fallback 'return vddci_table->entries[i-1].value' indexes entries[0xFFFFFFFF], reading at +0x7FFFFFFF8 (34,359,738,360 bytes ~ 32 GiB) past the heap object. The deref run of the verbatim function dies SIGSEGV(139); in-kernel this is a guaranteed page fault / panic during SMC table population at driver load. count==0 is reachable from a crafted VBIOS: atomctrl_get_voltage_table_v3 (ppatomctrl.c:546-563) accepts ucGpioEntryNum==0 (assert is only <=32) and returns success, smu7_construct_voltage_tables accepts it for BY_GPIO vddci_control, and polaris10/vegam/fiji populators invoke the lookup whenever a dep-table entry has vddci==0. Patched twin (count==0 guard) returns the caller's vddci with no wild access; normal path unchanged.
No comments yet.