β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4554

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4554 Β· 8 files
FileTypeDescriptionSize
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)

  1. sys/dev/drm/kconfig.h does not define CONFIG_DEBUG_FS; the entire handler body (amdgpu_debugfs.c:70-932) is inside #if defined(CONFIG_DEBUG_FS), so only the #else stubs (amdgpu_debugfs.c:933-942) compile.
  2. The DFly compat shim sys/dev/drm/include/linux/debugfs.h declares no functions at all (no debugfs_create_file), so even defining CONFIG_DEBUG_FS would not build the handler β€” the porting work does not exist yet.
  3. Runtime proof on the audit guest (see run.log): the shipped /boot/kernel/amdgpu.ko contains none of the handler symbols (amdgpu_debugfs_regs_read, _gpr_read, _wave_read, _sensor_read, _regs_pcie_*, …) β€” only amdgpu_debugfs_add_files plus the three #else stubs. Also, /sys/kernel/debug does 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.

VERDICT.md
↓ download raw

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

  1. Preprocessor: sys/dev/drm/kconfig.h (the compile-with include used by every drm file) defines CONFIG_DRM_* options but not CONFIG_DEBUG_FS. The whole handler block amdgpu_debugfs.c:70-932 is #if defined(CONFIG_DEBUG_FS); the compiled file is just the #else stubs (amdgpu_debugfs.c:933-942) plus the always-built amdgpu_debugfs_add_files() (whose inner #if at line 62 is also out).
  2. Shim: sys/dev/drm/include/linux/debugfs.h is 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 #if today would break the build, i.e. the port doesn't exist yet.
  3. Runtime (run.log, guest 2026-09-21): nm /boot/kernel/amdgpu.ko shows exactly amdgpu_debugfs_add_files, amdgpu_debugfs_regs_init/_regs_cleanup/_init (the #else stubs, and unrelated files' stubs) and no *_regs_read/_gpr_read/_wave_read/ _sensor_read/_regs_pcie_* symbols; strings shows no amdgpu_regs / amdgpu_gpr filenames; /sys/kernel/debug does not exist; no AMD GPU (pciconf -l | grep 1002 empty).

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_testable
baseline no→ patch + rebuild →patched clean

Not 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
↓ fix.diffper-fix-DF-4554

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.