Signed table_id bounds check allows OOB heap read/write in SMU10 table copy
Summary
smu10_copy_table_from_smc (smu10_smumgr.c:116) and _to_smc (line 144) take int16_t table_id; guard with signed "table_id < MAX_SMU_TABLE(=2)" at 121/149. Public API contract (hwmgr.h:218, smumgr.h:114, local wrapper smu10_smc_table_manager at 277) declares uint16_t. Caller passing table_id>=0x8000 wraps to negative int16_t, passes signed <2 check, indexes priv->smu_tables.entry[negative] OOB: _from_smc reads OOB entry fields (table ptr, size) then memcpy to caller buffer (arbitrary heap read); _to_smc writes attacker bytes to OOB-derived destination (arbitrary heap write). Identical class to DF-2034 (vega10) and DF-2021 (vega12). All current callers (smu10_hwmgr.c:427 SMU10_CLOCKTABLE=1, :1141 SMU10_WMTABLE=0) pass compile-time constants [0,1] so NOT directly triggerable from userspace today. Latent: one future/fuzzy caller with table_id>=0x8000 -> kernel heap corruption -> local priv-esc. Mailbox timeout robustness note: smu10_wait_for_response discards phm_wait_for_register_unequal return; send_msg always returns 0 -> silent firmware failure but subsequent memcpy uses kernel-set sizes no OOB. Lifecycle clean (init sets backend after kzalloc, fini NULLs). Fix: int16_t -> uint16_t parameter type.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2072 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | Full source-trace narrative (uint16_t/int16_t contract mismatch) + fix validation | 3.8 KB | β raw |
| README.md | readme | Original latent-primitive description | 1.1 KB | β raw |
| fix.diff | suggested-fix | git-apply-able: int16_t -> uint16_t in both copy functions | 705 B | view raw |
| build.sh | reproduce | Combined kernel build script | 755 B | view raw |
| run.sh | reproduce | Source-only confirmation; no runtime PoC | 543 B | view raw |
| fix_build.log | build-log | Full untrimmed combined kernel build (rc=0, 0 errors) | 5.6 MB | β download |
| env.txt | environment | uname, kern.version, cc version | 501 B | view raw |
DF-2072 PoC β SMU10 signed table_id OOB (latent / sibling of DF-2021/DF-2034)
Status: VERIFIED (source-only) + FIX VALIDATED
smu10_copy_table_from_smc/_to_smc take int16_t table_id
(smu10_smumgr.c:116/144) while their public API contract is
uint16_t (smumgr.h:114, hwmgr.h:218, local wrapper at :277).
A caller passing table_id=0xFFFF narrows to int16_t -1, passes the
signed table_id < MAX_SMU_TABLE(=2) guard, and indexes
priv->smu_tables.entry[-1] OOB. .size controls memcpy length,
.table/.mc_addr control source/destination.
Latent: both SMU10 in-tree callers pass compile-time constants in
[0,1]. Same pattern as DF-2021 (vega12) and DF-2034 (vega10).
See VERDICT.md for the source trace and fix validation.
Reproduce
./build.sh # rebuilds the patched kernel (rc=0 with -Werror) ./run.sh # source-only confirmation; no runtime PoC (latent)
Fix
fix.diff widens the parameter type from int16_t to uint16_t in
both copy functions, matching the public API contract. The
PP_ASSERT_WITH_CODE(table_id < MAX_SMU_TABLE, ...) guard then operates
on an unsigned value, so 0xFFFF < 2 correctly fails.
DF-2072 β REPRODUCED (source-confirmed, latent) + FIX VALIDATED
Verdict
REPRODUCED via source-only trace. The signed int16_t table_id
parameter in smu10_copy_table_from_smc / _to_smc lets a caller-passed
uint16_t value of 0xFFFF narrow to int16_t -1, sail through the
table_id < MAX_SMU_TABLE guard, and index the entry[] array at [-1]
OOB. Same pattern as DF-2021 (vega12) and DF-2034 (vega10). Latent
(no in-tree caller forwards an attacker-controlled table_id).
Mechanism (path:line)
The two copy functions take int16_t table_id but their public API
contract is uint16_t:
sys/dev/drm/amd/powerplay/smumgr/smu10_smumgr.c:115-116c static int smu10_copy_table_from_smc(struct pp_hwmgr *hwmgr, uint8_t *table, int16_t table_id) /* <-- signed */sys/dev/drm/amd/powerplay/smumgr/smu10_smumgr.c:143-144c static int smu10_copy_table_to_smc(struct pp_hwmgr *hwmgr, uint8_t *table, int16_t table_id) /* <-- signed */
The bound check uses signed comparison:
smu10_smumgr.c:121and:149c PP_ASSERT_WITH_CODE(table_id < MAX_SMU_TABLE, "Invalid SMU Table ID!", return -EINVAL;);
MAX_SMU_TABLE is 2 (smu10_smumgr.h:30) and entry[] is a
2-element array (smu10_smumgr.h:42).
The public API contract is uint16_t in three places:
sys/dev/drm/amd/powerplay/inc/smumgr.h:114extern int smum_smc_table_manager(struct pp_hwmgr *, uint8_t *, uint16_t, bool);sys/dev/drm/amd/powerplay/inc/hwmgr.h:218int (*smc_table_manager)(struct pp_hwmgr *, uint8_t *, uint16_t, bool);sys/dev/drm/amd/powerplay/smumgr/smu10_smumgr.c:277c static int smu10_smc_table_manager(struct pp_hwmgr *hwmgr, uint8_t *table, uint16_t table_id, bool rw)
So when smu10_smc_table_manager (uint16_t) calls
smu10_copy_table_from_smc (int16_t) with table_id = 0xFFFF, the
implicit narrowing converts 0xFFFF to int16_t -1. Then
-1 < MAX_SMU_TABLE(=2) is true (signed compare), so the guard passes,
and priv->smu_tables.entry[-1] is read OOB. The OOB .size controls
the memcpy length; the OOB .table/.mc_addr control source and
destination. Net: arbitrary kernel heap read (_from_smc) or heap
write (_to_smc).
Reachability / impact ceiling
Latent. Both SMU10 callers (smu10_hwmgr.c:427 with
SMU10_CLOCKTABLE=1 and :1141 with SMU10_WMTABLE=0) pass
compile-time constants in [0,1]. No sysfs/ioctl/debugfs path forwards
a user-controlled table_id for the SMU10 driver. If a future code path
routed a user- or firmware-controlled 16-bit selector through
smum_smc_table_manager(hwmgr, buf, 0xFFFF, true), the result would be
a heap OOB read/write β the same primitive as DF-2021/DF-2034.
Fix
fix.diff widens the parameter type from int16_t to uint16_t in
both copy functions, matching the public API contract. The
PP_ASSERT_WITH_CODE(table_id < MAX_SMU_TABLE, ...) guard now operates
on an unsigned value, so 0xFFFF < 2 correctly fails and returns
-EINVAL before the OOB index.
Matches the finding markdown's recommendation (int16_t β uint16_t
parameter type in both copy functions).
Phase-8 build validation
Combined kernel + modules build (DF-2068 / DF-2069 / DF-2070 / DF-2071 / DF-2072) on DragonFly 6.5-DEVELOPMENT #0 baseline:
=== NK_DONE rc=0 ===(2026-07-25 11:31:20 UTC)0error:lines in the full 35,696-line build logsmu10_smumgr.cis part ofamdgpu.ko; the patched TU compiled clean with the module's default-Wno-pointer-sign -Werrorflags and linked intoamdgpu.ko(verified:smu10_smumgr.opresent).
The default kernel build IS a -Werror build; see fix_build.log and
env.txt.
Reproduce
./build.sh # rebuilds the patched kernel (rc=0 with -Werror) ./run.sh # source-only confirmation; no runtime PoC (latent)
Fix verification
fixedVALIDATED: combined kernel build rc=0 -Werror
VALIDATED: combined kernel build rc=0 -Werror
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
Source-confirmed: smu10_copy_table_from/to_smc int16_t table_id vs uint16_t API contract. Latent (callers pass constants).
Verified recommended fix
Source-confirmed: smu10_copy_table_from/to_smc int16_t table_id vs uint16_t API contract. Latent (callers pass constants).
Verdict
Source-confirmed: smu10_copy_table_from/to_smc int16_t table_id vs uint16_t API contract. Latent (callers pass constants).
No comments yet.