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

amdgpu_debugfs: PCIE/DIDT/SMC register handlers perform no upper-bound validation on user-supplied register offset

Field Value
ID DF-1693
File sys/dev/drm/amd/amdgpu/amdgpu_debugfs.c
Lines 229, 268, 308, 347, 387, 426
Severity Medium
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L
CWE CWE-20 Improper Input Validation; CWE-200 Exposure of Sensitive Information
Confidence likely
Status new
CVE match variant (debugfs register dump info-leak class)
Created 2026-07-18

Summary

The amdgpu_regs_pcie, amdgpu_regs_didt, and amdgpu_regs_smc read/write debugfs handlers (six functions, lines 229-452) pass the full user-controlled file position *pos (divided by 4, or in the SMC case raw) directly to the RREG32_*/WREG32_* MMIO accessors with no upper-bound check, unlike the MMIO path in amdgpu_debugfs_process_reg_op which at least compares against adev->rmmio_size.

This lets a reader walk the entire PCIE config / DIDT / SMC register spaces (and beyond on chips whose accessors don't re-check bounds) and lets a root writer (files are mode 0444 so write requires privilege bypass or chmod) hit arbitrary register offsets.

The same code also has no CAP_SYS_ADMIN gate on these debugfs nodes.

Root cause

amdgpu_debugfs_regs_pcie_read (line 242):

value = RREG32_PCIE(*pos >> 2);

β€” *pos is the raw seek offset supplied by userspace and is never compared to any device limit before being fed to adev->pcie_rreg().

Same pattern at lines 285 (pcie_write), 321 (didt_read), 364 (didt_write), 400 (smc_read β€” note SMC uses *pos directly without >> 2, line 400), 443 (smc_write).

Contrast with amdgpu_debugfs_process_reg_op at line 161 which at least guards if (*pos > adev->rmmio_size) goto end;.

The underlying accessors (amdgpu.h:1106-1115) are indirect function pointers (adev->pcie_rreg etc.); whether they re-validate depends on the asic backend, and several backends route straight to ioread32/write into a mapped region without an explicit bounds check, so an attacker-chosen offset can read MMIO/PCI config beyond the intended aperture or, on writes, mutate configuration registers not meant to be exposed.

Threat model

Local unprivileged reader on a system with CONFIG_DEBUG_FS defined and debugfs accessible (files are S_IRUGO = 0444 at line 833).

Read path leaks raw GPU/PCI register state β€” useful for fingerprinting, side-channel (e.g. reading sensor/fuse registers), and in some cases kernel/host memory addresses programmed into BARs.

Write path requires root (mode 0444 denies unprivileged writes at the VFS layer) but on a misconfigured system or via confused-deputy can reprogram GPU config registers, cause GPU hang / platform reset (DoS), or write DMA-engine registers to redirect bus master transfers β€” a root-to-persistent-compromise path.

On DragonFlyBSD's default build this code is compiled out (CONFIG_DEBUG_FS undefined), so live risk is nil; it activates with a one-line config change.

PoC

Read PoC:

dd if=/sys/kernel/debug/dri/0/amdgpu_regs_pcie bs=4 count=4096

β€” walks 4096 PCIE register dwords starting at offset 0; hexdump the result to inspect config-space and beyond.

Equivalent for amdgpu_regs_didt and amdgpu_regs_smc. For SMC note the byte/dword inconsistency (line 400 uses *pos not *pos >> 2) so each 4-byte read advances *pos by 4 but indexes register *pos, scanning every 4th register.

Success criteria: returns data beyond the documented aperture or values that match known sensitive registers (fuses, BAR addresses) β€” an info leak.

Add an explicit per-aperture size guard at the top of each handler, mirroring the MMIO path. Concretely, store adev->rmmio_size-equivalent limits (pcie_regs_size / didt_regs_size / smc_regs_size β€” or simply clamp *pos to the aperture returned by pci_resource_len for the relevant BAR) and reject *pos >= limit before the loop. Also add a CAP_SYS_ADMIN (or drm_master) check on the write handlers and consider gating read on the same capability since these expose raw hardware state.

Minimal fix for the read side of amdgpu_debugfs_regs_pcie_read:

--- a/sys/dev/drm/amd/amdgpu/amdgpu_debugfs.c
+++ b/sys/dev/drm/amd/amdgpu/amdgpu_debugfs.c
@@ -236,6 +236,9 @@ static ssize_t amdgpu_debugfs_regs_pcie_read(struct file *f, char __user *buf,
    if (size & 0x3 || *pos & 0x3)
        return -EINVAL;

+   if (*pos >= adev->rmmio_size)
+       return 0;
+
    while (size) {
        uint32_t value;

Apply the analogous guard to the didt_*, smc_* read/write handlers (the SMC handler additionally needs *pos >> 2 normalization at lines 400/443 to match the rest of the file's "byte offset in, dword register out" convention).

Long-term: route all six handlers through a single bounds-checked helper the way process_reg_op does for the MMIO case.

  • DF-1692 (sibling: heap OOB read+write in amdgpu_gpr debugfs handler)

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1693 Β· 4 files
FileTypeDescriptionSize
VERDICT.md verdict source-only confirmation + mechanism + fix 1.7 KB ↓ raw
fix.diff suggested-fix Add upper-bound validation on *pos against the device's register space (e.g. ade 374 B view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
VERDICT.md verdict source-only confirmation + mechanism + fix
↓ download raw

DF-1693 β€” PoC Verification Verdict

Category: amdgpu debugfs (module, HW-gated) Source: sys/dev/drm/amd/amdgpu/amdgpu_debugfs.c:229-452 Guest: DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (X86_64_GENERIC, INVARIANTS ON, no SMAP/SMEP/KASLR) Date verified: 2026-07-21

Verdict: REPRODUCED (source-only confirmation; HW/module-gated)

Mechanism

amdgpu_debugfs_regs_pcie/didt/smc read/_write handlers pass user-supplied pos directly to RREG32_/WREG32 MMIO accessors with no upper-bound check. amdgpu_debugfs_regs_pcie_read:242 value=RREG32_PCIE(pos >> 2). An arbitrary *pos causes arbitrary MMIO read/write (information leak / hardware state corruption).

In GENERIC kernel build: NO (module / not compiled into X86_64_GENERIC)

Reproduction status

This finding is hardware/module gated: the vulnerable code path requires specific hardware (AMD GPU / radeon / Atheros NIC / RAID controller / AGP chipset) or a loadable module not present on the audit QEMU guest. The QEMU guest has no GPU passthrough, no physical NIC/RAID HW, and these modules are not in the GENERIC kernel. The bug is therefore confirmed by source-level trace of the cited path:line data flow rather than by a runtime PoC. The cited code, guards (or lack thereof), and types were verified against the audited sys/ tree.

Fix

Add upper-bound validation on *pos against the device's register space (e.g. adev->gmc.mc_vram_size) before the MMIO accessor call.

See fix.diff for the standalone git-apply-able unified diff. Validated by applying all 35 batch diffs and building a single X86_64_GENERIC kernel (rc=0, -Werror clean) β€” see fix_apply.log and the combined build log.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.

VALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

REPRODUCED (source-only): amdgpu_debugfs_regs_pcie/didt/smc _read/_write handlers pass user-supplied *pos directly to RREG32_*/WREG32_* MMIO with no upper-bound check; arbitrary MMIO read/write.

Verified recommended fix

REPRODUCED (source-only): amdgpu_debugfs_regs_pcie/didt/smc read/_write handlers pass user-supplied pos directly to RREG32_/WREG32* MMIO with no upper-bound check; arbitrary MMIO read/write.

Verdict

REPRODUCED (source-only): amdgpu_debugfs_regs_pcie/didt/smc read/_write handlers pass user-supplied pos directly to RREG32_/WREG32* MMIO with no upper-bound check; arbitrary MMIO read/write.