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

amdgpu_gfx_kiq_init calls amdgpu_bo_unreserve on unreserved BO (ttm reservation imbalance)

  • File: sys/dev/drm/amd/amdgpu/amdgpu_gfx.c
  • Lines: 299–303
  • Severity: Info
  • CVSS 3.1: CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U:C:N/I:N/A:L
  • CWE: CWE-664 Improper control of a resource through its lifetime, CWE-820 missing sync of paired lock/unlock
  • Confidence: likely
  • Status: new

Summary

In amdgpu_gfx_kiq_init, if amdgpu_bo_reserve(kiq->eop_obj, true) returns non-zero, the code only dev_warn's and then unconditionally falls through to amdgpu_bo_kunmap + amdgpu_bo_unreserve.

Calling amdgpu_bo_unreserve (β†’ ttm_bo_unreserve) on a BO that was never reserved is an unbalanced reservation that TTM treats as a misuse.

Root cause

amdgpu_gfx.c:299-303:

r = amdgpu_bo_reserve(kiq->eop_obj, true);
if (unlikely(r != 0))
    dev_warn(...);            // no return / no goto
amdgpu_bo_kunmap(kiq->eop_obj);
amdgpu_bo_unreserve(kiq->eop_obj);

The error branch lacks return r; (or a goto skipping unreserve).

amdgpu_bo_unreserve (amdgpu_object.h:165-168) unconditionally calls ttm_bo_unreserve(&bo->tbo) with no held-reservation check, so on reserve failure this releases a reservation that is not held.

Threat model

Effectively unreachable. The BO was just created and pinned by amdgpu_bo_create_kernel at lines 289-291; nothing else can hold its reservation, so amdgpu_bo_reserve(..., true) succeeds in practice.

If it ever did fail (e.g. -ENOMEM deep in ttm_bo_reserve under extreme memory pressure), the result is a TTM reservation imbalance / kernel warning, i.e. local DoS at worst, during driver load β€” not controllable by an unprivileged user.

Return on reserve failure so kunmap/unreserve are only called when the reservation is actually held:

--- a/sys/dev/drm/amd/amdgpu/amdgpu_gfx.c
+++ b/sys/dev/drm/amd/amdgpu/amdgpu_gfx.c
@@ -299,7 +299,7 @@ int amdgpu_gfx_kiq_init(struct amdgpu_device *adev,
    r = amdgpu_bo_reserve(kiq->eop_obj, true);
    if (unlikely(r != 0)) {
        dev_warn(adev->dev, "(%d) reserve kiq eop bo failed\n", r);
-   }
+       return r;
+   }
    amdgpu_bo_kunmap(kiq->eop_obj);
    amdgpu_bo_unreserve(kiq->eop_obj);

(Note: the upstream Linux version of this routine does not reserve at all here; matching that is also acceptable.)

References

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1998 Β· 3 files
FileTypeDescriptionSize
VERDICT.md verdict source-only confirmation + mechanism + fix 1.6 KB ↓ raw
fix.diff suggested-fix Move amdgpu_bo_kunmap/unreserve inside if (r == 0) branch. 667 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-1998 β€” PoC Verification Verdict

Category: drm (module / HW-gated) Source: sys/dev/drm/amd/amdgpu/amdgpu_gfx.c:299-303 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_kiq_init: r = amdgpu_bo_reserve(kiq->eop_obj, true); if (r != 0) dev_warn(...); amdgpu_bo_kunmap(kiq->eop_obj); amdgpu_bo_unreserve(kiq->eop_obj); The unreserve runs unconditionally even when reserve failed β€” releasing a reservation the caller doesn't hold.

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

Move amdgpu_bo_kunmap/unreserve inside if (r == 0) branch.

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_kiq_init: r=amdgpu_bo_reserve(kiq->eop_obj,true); if (r!=0) dev_warn(...); amdgpu_bo_kunmap(kiq->eop_obj); amdgpu_bo_unreserve(kiq->eop_obj); unreserve runs uncond

Verified recommended fix

REPRODUCED (source-only): amdgpu_gfx_kiq_init: r=amdgpu_bo_reserve(kiq->eop_obj,true); if (r!=0) dev_warn(...); amdgpu_bo_kunmap(kiq->eop_obj); amdgpu_bo_unreserve(kiq->eop_obj); unreserve runs unconditionally even when reserve failed.

Verdict

REPRODUCED (source-only): amdgpu_gfx_kiq_init: r=amdgpu_bo_reserve(kiq->eop_obj,true); if (r!=0) dev_warn(...); amdgpu_bo_kunmap(kiq->eop_obj); amdgpu_bo_unreserve(kiq->eop_obj); unreserve runs unconditionally even when reserve failed.