amdgpu_debugfs: gpr_read VGPR path copies an UNINITIALIZED 4KB kmalloc to userspace when read_wave_vgprs is NULL (gfx_v7/v8) β independent of DF-1692; latent (dead code on DFly)
Summary
gpr_read allocates 1024 unzeroed dwords and fills them only if the bank's callback exists, but the copy-out runs unconditionally - only gfx_v9 defines read_wave_vgprs, so on CIK/VI a bank-0 read returns up to 4KB of uninitialized kernel heap. Orthogonal to DF-1692 (persists with offset=0 even after that fix). Latent on DFly: the whole file is dead code proven 3 ways (CONFIG_DEBUG_FS undefined; shim declares no functions; nm of shipped amdgpu.ko shows only #else stubs). Guard-like-wave_read fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4554 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.6 KB | β raw | |
| VERDICT.md | β | 3.8 KB | β raw | |
| run.sh | β | 254 B | view raw | |
| run.log | β | 790 B | view raw | |
| env.txt | β | 418 B | view raw | |
| fix.diff | β | 1.2 KB | view raw | |
| manifest.json | β | 1.2 KB | view raw | |
| verdict.json | β | 2.8 KB | view raw |
DF-4554 β amdgpu_debugfs_gpr_read: VGPR path copies an UNINITIALIZED 4 KB heap buffer to userspace when read_wave_vgprs is NULL (gfx_v6/v7/v8) β independent of DF-1692
What this is
sys/dev/drm/amd/amdgpu/amdgpu_debugfs.c, amdgpu_debugfs_gpr_read()
(lines 689-747):
data = kmalloc_array(1024, sizeof(*data), GFP_KERNEL); /* 710: NOT zeroed */
...
if (bank == 0) {
if (adev->gfx.funcs->read_wave_vgprs) /* 719 */
adev->gfx.funcs->read_wave_vgprs(...); /* 720 */
} else {
if (adev->gfx.funcs->read_wave_sgprs)
adev->gfx.funcs->read_wave_sgprs(...);
}
...
while (size) { /* 729 */
uint32_t value;
value = data[offset++]; /* 732: copied
regardless */
r = put_user(value, (uint32_t *)buf);
...
}
In this tree only gfx_v9_0.c defines .read_wave_vgprs
(gfx_v9_0.c:1383); gfx_v7_0_gfx_funcs (gfx_v7_0.c:4283-4290) and
gfx_v8_0_gfx_funcs (gfx_v8_0.c:5325-5332) have .read_wave_data and
.read_wave_sgprs only β so on CIK/VI asics the VGPR callback pointer is
NULL, the buffer is never filled, and the copy-out loop still ships
size dwords of uninitialized kernel heap to userspace. This survives
a fix of DF-1692's byte/dword unit confusion: even with offset = 0 and
size = 4096 exactly, the leak discloses a full uninitialized 4 KB
kmalloc allocation. (With DF-1692 unfixed the same loop is additionally
OOB β that part is DF-1692's, not re-reported here.)
CWE-908 / CWE-200 (uninitialized memory disclosure). The SGPR path is not
affected (all three asic families define read_wave_sgprs).
Reachability on DragonFly β DEAD CODE (proven three ways)
sys/dev/drm/kconfig.hdoes not defineCONFIG_DEBUG_FS; the entire handler body (amdgpu_debugfs.c:70-932) is inside#if defined(CONFIG_DEBUG_FS), so only the#elsestubs (amdgpu_debugfs.c:933-942) compile.- The DFly compat shim
sys/dev/drm/include/linux/debugfs.hdeclares no functions at all (nodebugfs_create_file), so even definingCONFIG_DEBUG_FSwould not build the handler β the porting work does not exist yet. - Runtime proof on the audit guest (see run.log): the shipped
/boot/kernel/amdgpu.kocontains none of the handler symbols (amdgpu_debugfs_regs_read,_gpr_read,_wave_read,_sensor_read,_regs_pcie_*, β¦) β onlyamdgpu_debugfs_add_filesplus the three#elsestubs. Also,/sys/kernel/debugdoes not exist on DFly, and the guest has no AMD GPU (pciconf -l | grep 1002β none).
So this is a latent finding: real in the source, activates only for the day a debugfs port lands and CONFIG_DEBUG_FS is enabled (and then only on CIK/VI hardware as a 0444 debugfs file readable by root β debugfs roots are root-only), plus it equally applies to upstream Linux amdgpu of this vintage.
Reproduce (on a system where the handlers ARE compiled β e.g. Linux with CONFIG_DEBUG_FS, CIK/VI GPU)
lseek(fd_amdgpu_gpr, (0ULL << 60) /* bank=0 VGPR */ , SEEK_SET); read(fd_amdgpu_gpr, buf, 4096); /* -> 4KB uninitialized heap */
Expected (bug): returned bytes are uninitialized kmalloc contents (heap pointers/old data β check for non-zero kernel-pointer-like patterns). Expected (fix): -EINVAL (no VGPR read support) or zeroed data.
Verdict
not_reproduced on this guest (feature compiled out + no AMD GPU);
source-level: certain that the loop copies without checking whether the
callback ran; likely for the overall finding given the dead-code caveat.
DF-4554 VERDICT β not_reproduced on DFly (dead code, proven); latent uninit-heap disclosure on CIK/VI
Narrative
amdgpu_debugfs_gpr_read() allocates data = kmalloc_array(1024, 4,
GFP_KERNEL) (amdgpu_debugfs.c:710) β not zeroed β then fills it only if
the selected bank's callback exists:
- bank == 0 (VGPR):
if (adev->gfx.funcs->read_wave_vgprs)(line 719) - bank == 1 (SGPR):
if (adev->gfx.funcs->read_wave_sgprs)(line 722)
but the copy-out loop (lines 729-742) runs unconditionally, putting
size dwords of data to userspace. Callback existence per asic in this
tree:
| asic family | gfx funcs | read_wave_data | read_wave_sgprs | read_wave_vgprs |
|---|---|---|---|---|
| gfx_v7 (CIK) | gfx_v7_0.c:4283 | yes | yes | NO |
| gfx_v8 (VI) | gfx_v8_0.c:5325 | yes | yes | NO |
| gfx_v9 (SOC15) | gfx_v9_0.c:1378 | yes | yes | yes |
So on CIK/VI, a bank-0 (VGPR) read of any size returns uninitialized
kernel heap β a 4 KB kmalloc's prior contents (heap addresses, freed
object data). This is independent of DF-1692: even with DF-1692's
offset-unit confusion fixed and size clamped to 4096 with offset 0, the
disclosure persists, because the fill never happened. (DF-1692 covers the
OOB read/write of this same loop via the byte/dword confusion; the
uninitialized-memory primitive is the part reported here.)
Contrast with the sibling amdgpu_debugfs_wave_read() (lines 641-648)
which guards with if (!x) return -EINVAL when the callback produced
nothing β the gpr handler has no equivalent guard.
Why not reproduced on this guest β dead code, three independent proofs
- Preprocessor:
sys/dev/drm/kconfig.h(the compile-with include used by every drm file) definesCONFIG_DRM_*options but notCONFIG_DEBUG_FS. The whole handler block amdgpu_debugfs.c:70-932 is#if defined(CONFIG_DEBUG_FS); the compiled file is just the#elsestubs (amdgpu_debugfs.c:933-942) plus the always-builtamdgpu_debugfs_add_files()(whose inner#ifat line 62 is also out). - Shim:
sys/dev/drm/include/linux/debugfs.his an include-only header (26-32: includes, no declarations);debugfs_create_file(used at line 832) does not exist anywhere in the DFly shim β enabling the#iftoday would break the build, i.e. the port doesn't exist yet. - Runtime (run.log, guest 2026-09-21):
nm /boot/kernel/amdgpu.koshows exactlyamdgpu_debugfs_add_files,amdgpu_debugfs_regs_init/_regs_cleanup/_init(the#elsestubs, and unrelated files' stubs) and no*_regs_read/_gpr_read/_wave_read/ _sensor_read/_regs_pcie_*symbols;stringsshows noamdgpu_regs/amdgpu_gprfilenames;/sys/kernel/debugdoes not exist; no AMD GPU (pciconf -l | grep 1002empty).
Impact if the code ever activates
Debugfs nodes are created S_IFREG | S_IRUGO (amdgpu_debugfs.c:833) under
the card's debugfs root (root-only traversal on Linux), so the realistic
actor is root/local-privileged on a CIK/VI system β an info-leak of
uninitialized heap to a reader, useful to defeat KASLR-style hardening on
kernels that have it. Recorded as Low severity latent hardening gap; the
same defect exists in upstream Linux amdgpu of this vintage.
Fix
Zero the allocation (kzalloc) or, better, mirror wave_read's guard:
return -EINVAL when the requested bank's callback is absent, and
validate size/offset against the 1024-dword buffer (the latter is
DF-1692's fix). See fix.diff.
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: handlers are compiled out on DFly and the guest has no AMD GPU; fix.diff is source-reviewed and git-apply-verified only
fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log sections =C/=D/=E/=: nm of /boot/kernel/amdgpu.ko shows only amdgpu_debugfs_add_files and the #else stubs (amdgpu_debugfs_init/_regs_init/_regs_cleanup), no handler symbols; no amdgpu_regs/amdgpu_gpr strings; no /sys/kernel/debug; no AMD GPU. Source: gfx_v7_0.c:4283-4290 and gfx_v8_0.c:5325-5332 lack .read_wave_vgprs; contrast wave_read's !x guard at amdgpu_debugfs.c:647.
PoC changes
authored from scratch this pass (no seed; finding filed this pass)
Verified recommended fix
Fail the read (-EINVAL) when the selected bank's callback is absent instead of copying an unfilled buffer (see fix.diff)
Verdict
Latent uninitialized-heap disclosure: amdgpu_debugfs_gpr_read() kmallocs 1024 dwords (unzeroed, line 710) and fills them only if the bank's callback exists (lines 718-724), but the copy-out loop (729-742) runs unconditionally. Only gfx_v9_0.c defines .read_wave_vgprs in this tree, so on CIK/VI (gfx_v7_0.c:4283, gfx_v8_0.c:5325) a bank-0 VGPR read returns 4KB of uninitialized kernel heap β independent of DF-1692's offset unit confusion and surviving its fix. NOT reachable on DragonFly: CONFIG_DEBUG_FS is undefined in sys/dev/drm/kconfig.h, the DFly linux/debugfs.h shim declares no functions (the #if body would not even build), and runtime proof shows the shipped amdgpu.ko contains no handler symbols (nm/strings, run.log). Not reproduced on the guest: dead code + no AMD GPU.
No comments yet.