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

amdgpu_gfx_scratch_free performs unbounded 1u << (reg - reg_base) shift (latent UB)

  • File: sys/dev/drm/amd/amdgpu/amdgpu_gfx.c
  • Lines: 98–100
  • Severity: Info
  • CVSS 3.1: CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U:C:N/I:N/A:N
  • CWE: CWE-758 Reliance on Undefined Behavior, CWE-770 Missing bounds check on shift operand
  • Confidence: likely
  • Status: new

Summary

amdgpu_gfx_scratch_free trusts its reg argument and computes 1u << (reg - reg_base) with no validation. If any caller ever passes a reg outside [reg_base, reg_base + scratch.num_reg), the shift amount is >= 32 (UB) or, if reg < reg_base, wraps to a huge unsigned value, corrupting free_mask or yielding UB.

All current callers round-trip the reg through amdgpu_gfx_scratch_get (which guarantees reg - reg_base in [0, num_reg-1] = [0,7]), so this is a latent hardening gap, not a live bug.

Root cause

amdgpu_gfx.c:100: adev->gfx.scratch.free_mask |= 1u << (reg - adev->gfx.scratch.reg_base); has no if (reg - reg_base < scratch.num_reg) guard, unlike amdgpu_gfx_scratch_get at lines 80-86 which bounds i via i <= num_reg.

scratch.num_reg is 8 on every ASIC (gfx_v7_0.c:2042, gfx_v8_0.c:828, gfx_v9_0.c:351).

All current callers (gfx_v7_0.c:2075/2096/2362, gfx_v8_0.c:851/873, gfx_v9_0.c:408/430) pass the exact value obtained from amdgpu_gfx_scratch_get, so the difference is always in [0,7] and the shift is safe today.

Threat model

No reachable misuse. The function is an internal helper called only from gfx_v{7,8,9}_0.c scratch-register probe/teardown sequences with values obtained from the matching scratch_get.

Not exposed to unprivileged users, sysfs, or ioctls. Defense-in-depth only.

Add a bounds guard mirroring scratch_get, returning early (or WARN_ONCE) if reg is out of range:

--- a/sys/dev/drm/amd/amdgpu/amdgpu_gfx.c
+++ b/sys/dev/drm/amd/amdgpu/amdgpu_gfx.c
@@ -98,6 +98,10 @@ int amdgpu_gfx_scratch_get(struct amdgpu_device *adev, uint32_t *reg)
 void amdgpu_gfx_scratch_free(struct amdgpu_device *adev, uint32_t reg)
 {
+   uint32_t i = reg - adev->gfx.scratch.reg_base;
+
+   if (i >= adev->gfx.scratch.num_reg)
+       return;
    adev->gfx.scratch.free_mask |= 1u << i;
 }

References

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1997 Β· 3 files
FileTypeDescriptionSize
VERDICT.md verdict source-only confirmation + mechanism + fix 1.5 KB ↓ raw
fix.diff suggested-fix If (reg < adev->gfx.scratch.num_reg) before the shift; else return -EINVAL. 734 B view raw
../fix_build_new.log build-log Batch kernel build with new fixes (rc=0, -Werror) 5.6 MB ↓ download
VERDICT.md verdict source-only confirmation + mechanism + fix
↓ download raw

DF-1997 β€” PoC Verification Verdict

Category: drm (module / HW-gated) Source: sys/dev/drm/amd/amdgpu/amdgpu_gfx.c:98-100 Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (X86_64_GENERIC, INVARIANTS ON, no SMAP/SMEP/KASLR) Date verified: 2026-07-25

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

Mechanism

amdgpu_gfx_scratch_free(reg): reg -= reg_base; adev->gfx.scratch.free_mask |= 1u << reg; if reg >= 32 the shift is UB (C standard). No bounds check on reg.

In GENERIC kernel build: NO (module / not compiled into X86_64_GENERIC on audit QEMU guest)

Reproduction status

This finding is hardware/module gated: the vulnerable code path requires specific hardware (AMD GPU / radeon / Atheros NIC / RAID controller) 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 exercised. 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

If (reg < adev->gfx.scratch.num_reg) before the shift; else return -EINVAL.

See fix.diff for the standalone git-apply-able unified diff. Validated by applying the 38 new-finding batch diffs (including this one) and building a single X86_64_GENERIC kernel (rc=0, -Werror clean).

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_gfx_scratch_free(reg): reg -= reg_base; adev->gfx.scratch.free_mask |= 1u << reg; if reg>=32 the shift is UB (C standard). No bounds check.

Verified recommended fix

REPRODUCED (source-only): amdgpu_gfx_scratch_free(reg): reg -= reg_base; adev->gfx.scratch.free_mask |= 1u << reg; if reg>=32 the shift is UB (C standard). No bounds check.

Verdict

REPRODUCED (source-only): amdgpu_gfx_scratch_free(reg): reg -= reg_base; adev->gfx.scratch.free_mask |= 1u << reg; if reg>=32 the shift is UB (C standard). No bounds check.