Unbounded write to stack array tmp_topology overflows when hive exceeds 4 nodes
Summary
amdgpu_xgmi_add_device declares tmp_topology[AMDGPU_MAX_XGMI_DEVICE_PER_HIVE]=4 entries (16 bytes each=64 bytes) on stack then fills it in unbounded list_for_each_entry loop over hive->device_list (tmp_topology[count++].device_id = entry->device_id) with NO check count<4. Device is list_add_tail-d to hive BEFORE the loop (line 85) so once hive holds >4 entries write at tmp_topology[4] and beyond smashes adjacent stack slots (hive/entry/tmp_adev/count/saved-RBP/return-addr). psp_v11_0_xgmi_get_hive_id (psp_v11_0.c:564-573) is dev stub returning SAME constant 0x123456789abcdef for every Vega20 GPU so all collapse into one software hive regardless of physical topology; 5th GPU tips list past array. Attacker: local privileged (root) or boot/probe. Function invoked once per Vega20 GPU during amdgpu_device_init (amdgpu_device.c:1691). Impact: deterministic kernel panic on psp_v11_0 (written value=0 so corrupting RBP/return-addr to 0 panics on return) or NULL deref at line 98 or masked failure.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2203 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | claim, source-trace pointers, fix summary | 3.6 KB | β raw |
| VERDICT.md | verdict | unbounded-write trace + stub hive-id collapse + bounds-check fix | 3.9 KB | β raw |
| fix.diff | suggested-fix | bound list_for_each_entry loop on count vs AMDGPU_MAX_XGMI_DEVICE_PER_HIVE | 1.2 KB | view raw |
| build.sh | build-script | applies fix.diff + rebuilds amdgpu.ko in guest | 538 B | view raw |
| run.sh | run-script | HW-gated no-op runner | 385 B | view raw |
| build_baseline.log | build-log | unpatched amdgpu.ko build, rc=0, -Werror | 1.0 MB | β download |
| build_patched.log | build-log | patched amdgpu.ko rebuild, only amdgpu_xgmi.o recompiled, rc=0, -Werror | 12.6 KB | view raw |
| env.txt | environment | uname, cc, kern.version | 376 B | view raw |
DF-2203 β amdgpu_xgmi unbounded write to stack tmp_topology (Medium)
Claim
amdgpu_xgmi_add_device() in
sys/dev/drm/amd/amdgpu/amdgpu_xgmi.c declares
tmp_topology[AMDGPU_MAX_XGMI_DEVICE_PER_HIVE] (4 entries, 16 bytes each,
64 bytes on stack) and fills it with an unbounded
list_for_each_entry(... hive->device_list ...) loop whose body is
tmp_topology[count++].device_id = entry->device_id;. Because the device
is list_add_tail()-ed into the hive before the loop (line 85), once the
hive aggregates more than 4 devices the write goes past the array and
smashes adjacent stack slots (return address, saved RBP, hive, entry).
The stub psp_v11_0_xgmi_get_hive_id() (psp_v11_0.c:564) returns the
same constant 0x123456789abcdef for every Vega20 GPU, so all of them
collapse into one software hive β a 5-GPU system tips the loop past the
array.
Verification approach
HW-gated / source-only. amdgpu_xgmi_add_device() is called once per
Vega20-class GPU from amdgpu_device_init() (amdgpu_device.c:1691) and
is reachable only when an amdgpu driver is bound to real Vega20 hardware.
No such hardware on this audit guest. Per the task brief, source-only
confirmation is acceptable. The overflow is fully visible at the source
level and the structural fix is mechanical:
- Confirm
tmp_topologyis sized byAMDGPU_MAX_XGMI_DEVICE_PER_HIVE(4). - Confirm the loop body has no
count < MAXguard. - Confirm the
list_add_tailprecedes the loop (so the just-added device is itself counted). - Confirm
psp_v11_0_xgmi_get_hive_id()is a stub returning a constant per device. - Author
fix.diff(bound the loop). - Phase 8 β apply all 5 batched fixes and rebuild
amdgpu.kowith-Werror.
Source trace (confirmed)
sys/dev/drm/amd/amdgpu/amdgpu_xgmi.c:32β#define AMDGPU_MAX_XGMI_DEVICE_PER_HIVE 4.sys/dev/drm/amd/amdgpu/amdgpu_xgmi.c:66βstruct psp_xgmi_topology_info tmp_topology[AMDGPU_MAX_XGMI_DEVICE_PER_HIVE];(4 entries, on stack).sys/dev/drm/amd/amdgpu/amdgpu_xgmi.c:85βlist_add_tail(&adev->gmc.xgmi.head, &hive->device_list);adds the current device before the counting loop.sys/dev/drm/amd/amdgpu/amdgpu_xgmi.c:86-87βlist_for_each_entry(entry, &hive->device_list, head) tmp_topology[count++].device_id = entry->device_id;β no upper bound oncount.sys/dev/drm/amd/amdgpu/amdgpu_psp.h:144βstruct psp_xgmi_topology_info { uint64_t device_id; uint32_t connection_mask; uint32_t reserved; }= 16 bytes per entry.sys/dev/drm/amd/amdgpu/psp_v11_0.c:564-573βpsp_v11_0_xgmi_get_hive_id()returns the constant0x123456789abcdeffor every Vega20 GPU wherenum_physical_nodes != 0.sys/dev/drm/amd/amdgpu/amdgpu_device.c:1691β call site: once per Vega20 GPU duringamdgpu_device_init.
Overflow scenario
A host with 5+ Vega20 GPUs all reporting the same stub hive id lands them
all in one xgmi_hives[] entry; the 5th device's list_for_each_entry
writes tmp_topology[4].device_id (16 bytes past the array), tmp_topology[5],
etc. On x86_64 the write immediately overruns the stack frame.
Files
VERDICT.mdβ full narrative.fix.diffβ guard the loop withif (count >= AMDGPU_MAX_XGMI_DEVICE_PER_HIVE) { dev_err(...); ret = -ENOSPC; goto exit; }.build_baseline.logβ unpatchedamdgpu.kobuild, rc=0,-Werror.build_patched.logβ patchedamdgpu.korebuild (onlyamdgpu_xgmi.orecompiled), rc=0,-Werror.env.txtβ guest environment.
Reproduce
./build.sh # applies fix.diff + rebuilds amdgpu.ko ./run.sh # HW-gated no-op (see VERDICT.md)
DF-2203 β VERDICT
Verdict: REPRODUCED (source-only, HW-gated).
Class: stack buffer overflow (memory corruption).
Mechanism
amdgpu_xgmi_add_device() (sys/dev/drm/amd/amdgpu/amdgpu_xgmi.c) builds
a transient on-stack topology snapshot for the XGMI hive:
/* amdgpu_xgmi.c:32 */
#define AMDGPU_MAX_XGMI_DEVICE_PER_HIVE 4
/* amdgpu_xgmi.c:66 */
struct psp_xgmi_topology_info tmp_topology[AMDGPU_MAX_XGMI_DEVICE_PER_HIVE];
/* amdgpu_xgmi.c:85 */
list_add_tail(&adev->gmc.xgmi.head, &hive->device_list);
/* amdgpu_xgmi.c:86-87 */
list_for_each_entry(entry, &hive->device_list, head)
tmp_topology[count++].device_id = entry->device_id;
tmp_topology is a fixed 4-entry array (4 Γ 16 bytes = 64 bytes on stack;
see struct psp_xgmi_topology_info at amdgpu_psp.h:144). The loop body
increments count unconditionally and writes tmp_topology[count++]
with no upper-bound check, so once hive->device_list holds more than 4
devices the write runs past the array into adjacent stack slots.
The list_add_tail() at line 85 happens before the loop, so the
device currently being added is counted too β the 5th add into a hive is
the one that overruns.
Why 5+ devices realistically land in one hive
psp_v11_0_xgmi_get_hive_id() is a stub (psp_v11_0.c:564-573):
static uint64_t psp_v11_0_xgmi_get_hive_id(struct psp_context *psp)
{
uint64_t hive_id = 0;
/* Remove me when we can get correct hive_id through PSP */
if (psp->adev->gmc.xgmi.num_physical_nodes)
hive_id = 0x123456789abcdef;
return hive_id;
}
It returns the same constant for every Vega20-class GPU that has
num_physical_nodes != 0. amdgpu_xgmi_add_device uses that value as
the hive lookup key (amdgpu_xgmi.c:77), so all such GPUs collapse into
one xgmi_hives[] entry. A host with 5+ Vega20 GPUs therefore exceeds
the 4-entry array on the 5th amdgpu_xgmi_add_device() call.
Stack layout impact
tmp_topology is the largest local in the function (64 bytes); writing
tmp_topology[4] and beyond overwrites other locals (hive, entry,
tmp_adev, count, ret) and ultimately the saved RBP / return
address. Impact per the original finding: deterministic kernel panic on
return (corrupted return address), NULL deref at line 98, or silent
misbehaviour depending on the values written.
Trigger surface / reachability
amdgpu_xgmi_add_device() is invoked from amdgpu_device_init()
(amdgpu_device.c:1691) exactly once per Vega20 GPU during driver probe.
The function early-returns for non-Vega20 ASICs (asic_type < CHIP_VEGA20)
and APUs. On this audit guest there is no GPU, so the overflow cannot be
exercised live; the bug is confirmed at the source level and the fix
compiles cleanly (Phase 8).
Fix
fix.diff adds a per-iteration bounds check inside the
list_for_each_entry loop:
list_for_each_entry(entry, &hive->device_list, head) {
if (count >= AMDGPU_MAX_XGMI_DEVICE_PER_HIVE) {
dev_err(adev->dev, "XGMI: hive %llx holds more than %d devices; "
"refusing to overflow tmp_topology", ...);
ret = -ENOSPC;
goto exit;
}
tmp_topology[count++].device_id = entry->device_id;
}
This is the minimal, targeted fix: refuse to overflow rather than silently
truncating. The correct long-term fix is also to drive num_physical_nodes
from real PSP data (kill the stub), but that is a separate, larger change
and out of scope for a single-finding diff.
Phase 8 build validation
Applied fix.diff (plus the four other batched DRM fixes) to the in-guest
/usr/src, rebuilt amdgpu.ko with -Werror:
* baseline (unpatched) amdgpu.ko: rc=0 (build_baseline.log).
* patched amdgpu.ko: rc=0, only amdgpu_xgmi.o recompiled
(build_patched.log); no warnings, no errors.
Verdict
REPRODUCED at source level (HW-gated; no live repro possible on guest).
The unbounded stack write is real and unambiguous from the source; the
fix compiles cleanly under -Werror.
Fix verification
fixedamdgpu.ko baseline+patched rc=0 -Werror
amdgpu.ko baseline+patched rc=0 -Werror
Confirmed kernel references
β
Detail
Exploit chain
none (HW-gated)
Evidence (decisive lines)
HW-gated (no Vega20). Source-confirmed: amdgpu_xgmi tmp_topology[4] overflow when >4 GPUs in hive.
Verified recommended fix
HW-gated (no Vega20). Source-confirmed: amdgpu_xgmi tmp_topology[4] overflow when >4 GPUs in hive.
Verdict
HW-gated (no Vega20). Source-confirmed: amdgpu_xgmi tmp_topology[4] overflow when >4 GPUs in hive.
No comments yet.