count==0 underflow: ptable->entries[ptable->count - 1] reads index 0xFFFFFFFF in smu8_dpm_update_vce_dpm / smu8_dpm_update_uvd_dpm (smu10_hwmgr.c:851 twin)
Summary
Both update paths index entries[count-1] with count the raw u8 numEntries from the VBIOS extension table. count==0 passes the only load-time guard (<= SMU8_MAX_HARDWARE_POWERLEVELS - 0 <= 8), init completes, and the first VCE/UVD ungate under StablePState or en_umd_pstate (user-reachable via the power_dpm_force_performance_level profile_* sysfs) computes entries[0xFFFFFFFF] - a wild kernel read ~16GB from the table base -> fault -> panic. The init-time helper rejects count<=0 but these runtime paths never re-check. smu8's own instance of the smu10:851 underflow already flagged. Zero-count guard fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4171 Β· 21 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.3 KB | β raw | |
| VERDICT.md | β | 6.6 KB | β raw | |
| env.txt | β | 1.3 KB | view raw | |
| fix.diff | β | 1.2 KB | view raw | |
| build.log | β | 2.7 KB | view raw | |
| run.log | β | 10.4 KB | view raw | |
| run.2.log | β | 10.4 KB | view raw | |
| run.3.log | β | 10.4 KB | view raw | |
| verdict.json | β | 7.0 KB | view raw | |
| manifest.json | β | 2.3 KB | view raw | |
| rig/gen_rig.sh | β | 7.2 KB | view raw | |
| rig/check_verbatim.sh | β | 5.5 KB | view raw | |
| rig/rig_shims.h | β | 7.0 KB | view raw | |
| rig/smu8_rig.c | β | 23.7 KB | view raw | |
| rig/Makefile | β | 448 B | β download | |
| rig/build.sh | β | 509 B | view raw | |
| rig/run.sh | β | 1.2 KB | view raw | |
| rig/smu8_slice.inc | β | 9.3 KB | β download | |
| rig/smu8_slice_fixed.inc | β | 9.2 KB | β download | |
| rig/smu8_slice_verbatim.h | β | 36.7 KB | view raw | |
| rig/rig_fix.diff | β | 1.1 KB | view raw |
DF-4171 β count==0 underflow: entries[count-1] reads before the table in smu8_dpm_update_vce_dpm / smu8_dpm_update_uvd_dpm
- File: sys/dev/drm/amd/powerplay/hwmgr/smu8_hwmgr.c sites: smu8_dpm_update_vce_dpm :1251-1278 (read at :1260-1261), smu8_dpm_update_uvd_dpm :1846-1874 (read at :1856-1857)
- count source: raw u8 numEntries from the VBIOS powerplay extension table
(processpptables.c get_vce/uvd_clock_voltage_limit_table :1114-1141);
the only load-time guard is
count <= SMU8_MAX_HARDWARE_POWERLEVELS(smu8_hwmgr.c:471-474), which accepts 0. - Triggers: PP_CAP(PHM_PlatformCaps_StablePState) OR hwmgr->en_umd_pstate (user-reachable: power_dpm_force_performance_level profile_* sysfs β pp_dpm_en_umd_pstate, amd_powerplay.c:310-335) on the first VCE/UVD DPM update after ungate.
- Class: kernel heap OOB read, CWE-125.
Verification (2026-10-15, verbatim-rig evidence class)
REPRODUCED in byte-identical production code on the DragonFly guest
(no AMD GPU β the in-kernel path is ASIC-gated; see VERDICT.md Β§1), with a
MECHANISM CORRECTION: count is uint8_t, so count - 1 promotes to int β1
and the access is entries[-1] at tableβ8 (entries at +4, record 12 B) β
NOT index 0xFFFFFFFF / ~16 GB. The rig prints the predicted address before
each run and the fault address matches exactly. In-kernel this is a silent
8-byte heap under-read feeding hard_min_clk/SMU messages, not a panic
(the finding's availability impact is overstated; see VERDICT.md Β§3).
cd rig && sh build.sh /usr/src # 26 verbatim blocks checker-proven, builds rig_base + rig_fixed sh run.sh # baseline + fixed (this pack's fix.diff) over 11 modes
Expected (see run.log):
- baseline
4171-vce-count0(StablePState cap) and4171-uvd-count0-en-umd(en_umd_pstate, the sysfs route): SIGSEGV READ classified "8 bytes BEFORE the start of the 8B table object: NEGATIVE-INDEX UNDERFLOW READ", at exactly the predicted tableβ8; - fixed build: both modes return rc=βEINVAL, no fault;
clean-validbyte-identical to baseline;4169-*modes still fault under this fix (orthogonal finding DF-4169).
Logs: build.log, run.log, run.2.log, run.3.log (three byte-identical runs). Full narrative: VERDICT.md. fix.diff authored against the read-only sys/ tree, A/B validated in the rig, never applied to sys/.
DF-4171 VERDICT (verify run, 2026-10-15)
Status: REPRODUCED with a MECHANISM CORRECTION (verbatim-rig evidence
class). The count==0 out-of-bounds read in both
smu8_dpm_update_vce_dpm and smu8_dpm_update_uvd_dpm was demonstrated
in byte-identical production code at the exact computed address β but it
is an entries[-1] NEGATIVE-INDEX read 8 bytes BEFORE the table object
(uint8_t count promotes to int β1), NOT the filed "index 0xFFFFFFFF /
~16 GB wild read / panic". The load-time guard passthrough (0 β€ 8) and
both user-reachable triggers (StablePState cap; en_umd_pstate via the
power_dpm_force_performance_level profile_* sysfs) are confirmed
in-tree. The pack's fix.diff was A/B-validated: fixed. In-kernel
execution is AMD-ASIC-gated on this guest (no AMD GPU β proven,
env.txt).
1. Method
Same rig as DF-4169 (same run, one guest): the cited production functions
extracted byte-identical from smu8_hwmgr.c
(:1251-1278 smu8_dpm_update_vce_dpm, :1846-1874
smu8_dpm_update_uvd_dpm + helpers :49-97/:99-158/:248-252 and the
DF-4169 cast/level sites), 26 blocks checker-verified on the guest against
/usr/src/sys (build.log: ALL VERBATIM CHECKS PASSED). The VCE/UVD
tables are built exactly as get_vce_clock_voltage_limit_table /
get_uvd_clock_voltage_limit_table build them (processpptables.c:1114-1141:
table_size = sizeof(unsigned long) + sizeof(table) * numEntries, count =
raw numEntries u8) and allocated page-aligned right after a PROT_NONE
page, so the first byte BEFORE the object faults: the underflow
detector.
2. Observed (run.log; byte-identical in run.2.log/run.3.log, md5-equal)
| mode | observation | verbatim site |
|---|---|---|
| 4171-vce-count0 | count=0 table (0 passes the only load-time guard count <= SMU8_MAX_HARDWARE_POWERLEVELS, :473), StablePState cap set β smu8_dpm_update_vce_dpm β SIGSEGV READ at tableβ8 β predicted address printed beforehand and matched exactly (entries at +4, record 12 B β entries[-1].ecclk at baseβ8) |
:1260-1261 |
| 4171-uvd-count0-en-umd | count=0 table, no StablePState cap, hwmgr->en_umd_pstate = true (the user sysfs route: power_dpm_force_performance_level β pp_dpm_en_umd_pstate, amd_powerplay.c:310-335) β smu8_dpm_update_uvd_dpm(hwmgr, false) β SIGSEGV READ at tableβ8 |
:1856-1857 |
| clean-valid | count=2 tables, stable+umd paths: hard_min_clk = entries[1].ecclk/vclk exactly, SMU msgs [0x20,0x1]/[0x1c,0x1], no fault |
:1260/:1856 |
Fixed (this pack's fix.diff, identical inputs): both modes return
rc=βEINVAL without faulting (if (!ptable || ptable->count == 0)
return -EINVAL; before both entries reads); clean-valid byte-identical;
the DF-4169 modes still fault under this fix (orthogonality β see
DF-4169's pack, whose FIXED section conversely leaves these two modes
faulting). Fix A/B validated.
3. Mechanism correction (why "0xFFFFFFFF" is wrong here)
ptable->count is uint8_t (struct phm_vce_clock_voltage_dependency_
table, hwmgr.h:183-186; same for uvd, hwmgr.h:137-141). In
ptable->entries[ptable->count - 1] the usual arithmetic promotions apply:
(int)count β 1 with count==0 is int β1, so the access is
entries[-1] β 8 bytes BEFORE the allocation (entries at offset 4, record
size 12). The ~16 GB/0xFFFFFFFF arithmetic would require a 32-bit count
type (that is the smu10_hwmgr.c:851 twin's shape). The rig proves the
actual address: predicted tableβ8 == faulting address, both modes.
Consequences of the correction:
- No panic in-kernel. The read lands inside the preceding slab page (kmalloc'd objects are surrounded by mapped kernel heap), so the realistic in-kernel behavior is a silent 8-byte heap under-read, not a fault. The finding's CVSS availability impact is overstated.
- What the bytes feed: the read value becomes
data->vce_dpm.hard_min_clk/data->uvd_dpm.hard_min_clkand is then passed to the SMU (SetEclkHardMin/SetUvdHardMin via the level helpers) β wrong-clock programming from heap garbage, i.e. an integrity/robust- ness defect in the DPM engine. No path returning the raw value to userland was found (the sysfs exposes level indices computed by the level helpers, not the clock), so a confidentiality leak is NOT demonstrated.
The security-relevant core of the finding β an unvalidated firmware u8 count that passes the only load-time check and indexes before the allocation at runtime, on a user-reachable trigger β is confirmed verbatim. The bucket classification (kernleak/CWE-125 OOB read) stands; the impact narrative (wild read/panic) is corrected to bounded negative-index OOB read.
4. Fix validation
rig_fixed = same slice + this pack's fix.diff (header-normalized apply,
bidirectional patch proof, hunk-body cmp-identity β see DF-4169 pack;
both packs' fixed builds were built on the guest in the same session).
Both 4171 modes guarded (rc=β22, no fault), clean-valid byte-identical,
4169 modes unaffected (orthogonal). Rebuilt-kernel validation not
performed (AMD-ASIC-gated on this guest); fix.diff never applied to the
read-only sys/ tree; guest /usr/src pristine before and after (md5
a0cb59b723434a78e88bf0580d5a5508).
5. Honest limitations
- Demonstrated in the rig (guard page before the object makes the underflow fault); in-kernel it would not fault β no in-kernel manifestation was observable on this guest (no AMD GPU), so the in-kernel impact statement rests on the promotion analysis + exact address match, not on a guest panic.
- The count==0 table is constructed directly (production table-builder's output for numEntries==0), not by parsing a full crafted VBIOS.
- The :454 hunk of this pack's fix.diff (upload-path NULL-table guard, DF-4170 residual) is outside the sliced functions and was not exercised by this rig.
6. Bottom line
The OOB read is real, reproduced in byte-identical production code at the
exact computed address, on both cited sites, under both cited triggers,
and the pack's zero-count guard eliminates it on identical input without
changing valid-table behavior. The filed mechanism (0xFFFFFFFF index,
~16 GB, panic) is corrected to entries[-1] (uint8_tβint promotion);
realistic impact is a bounded kernel-heap under-read feeding SMU clock
parameters, not a panic.
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
fixedA/B validated in the same rig: rig_fixed = same verbatim slice + this pack's fix.diff (forward apply --fuzz=0 no rejects, reverse apply restores pristine byte-exactly, hunk bodies cmp-identical). On identical inputs both 4171 modes return rc=-EINVAL with no fault/no underflow read; clean-valid output byte-identical to baseline (hard_min_clk entries[1].ecclk=300000/vclk=133300, SMU msgs [0x20,0x1]/[0x1c,0x1]); the DF-4169 modes still fault under this fix, proving orthogonality. Rebuilt-kernel validation not performed: AMD-ASIC-gated on this guest (verbatim-rig protocol).
run.log FIXED sections: '4171-vce-count0 rc=-22 / returned WITHOUT faulting (guard active in fixed build)' and same for '4171-uvd-count0-en-umd'; clean-valid lines identical to baseline; build.log: 'ALL VERBATIM CHECKS PASSED' incl. fixed-slice == pristine+fix.diff and reverse-apply restore proof
Confirmed kernel references
- sys/dev/drm/amd/powerplay/hwmgr/smu8_hwmgr.c:471
- sys/dev/drm/amd/powerplay/hwmgr/smu8_hwmgr.c:473
- sys/dev/drm/amd/powerplay/hwmgr/smu8_hwmgr.c:1260
- sys/dev/drm/amd/powerplay/hwmgr/smu8_hwmgr.c:1261
- sys/dev/drm/amd/powerplay/hwmgr/smu8_hwmgr.c:1856
- sys/dev/drm/amd/powerplay/hwmgr/smu8_hwmgr.c:1857
- sys/dev/drm/amd/powerplay/inc/hwmgr.h:137
- sys/dev/drm/amd/powerplay/inc/hwmgr.h:183
- sys/dev/drm/amd/powerplay/hwmgr/processpptables.c:1119
- sys/dev/drm/amd/powerplay/amd_powerplay.c:310
Detail
Exploit chain
crafted/corrupted VBIOS (or root-written pp_table blob) on a Carrizo/Bristol Ridge SMU8 APU ships a VCE or UVD clock/voltage limit table with numEntries==0 -> processpptables get_vce/uvd_clock_voltage_limit_table allocates the table with count=0 (the :471-474 load-time guard accepts 0) -> user writes profile_standard/profile_peak/... to power_dpm_force_performance_level -> pp_dpm_en_umd_pstate sets en_umd_pstate -> first VCE/UVD DPM update after ungate computes entries[count-1] = entries[-1] -> 8-byte kernel-heap read before the table object -> garbage hard_min_clk programmed to the SMU. Bounded negative-index OOB read; no demonstrated userland exfiltration, no in-kernel fault (mechanism corrected from the filed 0xFFFFFFFF/panic claim).
Evidence (decisive lines)
findings/poc/DF-4171/run.log: '[BASELINE] MODE 4171-vce-count0 -> :1261 reads entries[count-1].ecclk = entries[-1] at 0x800475ff8 (table-8 ...) -> !! SIGSEGV (addr=0x800475ff8, READ) => fault 8 bytes BEFORE the start of the 8B table object: NEGATIVE-INDEX UNDERFLOW READ'; same for '4171-uvd-count0-en-umd' at :1857; layout mode prints the promotion arithmetic ('uint8_t -> int -1 ... table - 8') and the guard passthrough ('count=0 <= 8 -> PASSES'); FIXED sections show rc=-22/no fault for both modes; build.log: 26 verbatim blocks ALL PASSED on the guest against /usr/src; run.2/run.3.log md5-identical
PoC changes
Seed pack had no runnable PoC. Built the verbatim rig per the DF-4388 pattern (same rig as DF-4169, run in the same guest session): gen_rig.sh/check_verbatim.sh extract and guest-verify 26 byte-identical blocks; tables are allocated with the production formula and page-aligned after a PROT_NONE leading-guard page so the underflow faults at the exact predicted address; modes added for both update paths and both triggers (StablePState cap; en_umd_pstate). Fixed build applies this pack's fix.diff (header-normalized, hunk bodies cmp-identical, bidirectional patch proof).
Verified recommended fix
Zero-count guard before both entries[count-1] reads: apply fix.diff (if (!ptable || ptable->count == 0) return -EINVAL; in smu8_dpm_update_vce_dpm :1258-1259 and smu8_dpm_update_uvd_dpm :1854-1855; the !ptable half also covers DF-4170's runtime residual).
Verdict
The count==0 out-of-bounds read was demonstrated in byte-identical production smu8_hwmgr.c code on the DragonFly guest (verbatim-rig evidence class; in-kernel execution is AMD-ASIC-gated, no AMD GPU in the guest), on both cited sites and under both cited triggers -- but with a MECHANISM CORRECTION that changes the impact ceiling. ptable->count is uint8_t (hwmgr.h:137-141 uvd / :183-186 vce), so 'ptable->entries[ptable->count - 1]' with count==0 promotes to int -1: the access is entries[-1] at exactly table-8 (entries at offset 4, record 12B), NOT index 0xFFFFFFFF at ~16GB. The rig prints the predicted address before the call and the fault lands on it exactly (SIGSEGV READ '8 bytes BEFORE the start of the 8B table object: NEGATIVE-INDEX UNDERFLOW READ'), for smu8_dpm_update_vce_dpm under the StablePState cap (:1260-1261) and for smu8_dpm_update_uvd_dpm under en_umd_pstate with no cap (:1856-1857) -- the user-reachable route (power_dpm_force_performance_level profile_* sysfs -> pp_dpm_en_umd_pstate, amd_powerplay.c:310-335, confirmed in-tree). count==0 passing the only load-time guard (count <= SMU8_MAX_HARDWARE_POWERLEVELS, :471-474) is shown arithmetically. Consequences of the correction: in-kernel the read lands in the preceding slab page, so it does NOT fault/panic (the finding's availability impact is overstated); the realistic manifestation is a silent 8-byte kernel-heap under-read whose value becomes vce_dpm/uvd_dpm.hard_min_clk and is programmed into the SMU as a hard-min clock (wrong-clock integrity defect). No userland readback of the raw value was found, so no confidentiality leak is demonstrated either -- the CWE-125 OOB-read class itself is confirmed verbatim. The pack's fix.diff was A/B-validated in the same rig: both modes return -EINVAL with no fault, clean-valid byte-identical, and the DF-4169 modes keep faulting under it (orthogonality). Three runs byte-identical.
No comments yet.