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

amdgpu_gfx_compute_mqd_sw_fini kfrees mqd_backup without NULLing the slot (double-free landmine)

  • File: sys/dev/drm/amd/amdgpu/amdgpu_gfx.c
  • Lines: 366, 373
  • 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-415 Double free, CWE-911 Missing release of pointer after use
  • Confidence: speculative
  • Status: new

Summary

amdgpu_gfx_compute_mqd_sw_fini calls kfree(adev->gfx.mec.mqd_backup[i]) for each compute ring and for the KIQ slot [AMDGPU_MAX_COMPUTE_RINGS], but never sets the slot back to NULL.

The BO free on the same ring is idempotent (amdgpu_bo_free_kernel NULL-guards and amdgpu_bo_unref NULLs the pointer), but the raw kfree is not.

If the GFX IP-block sw_fini ever executed twice on the same adev, the second pass would double-free the same mqd_backup pointers.

Root cause

amdgpu_gfx.c:366: kfree(adev->gfx.mec.mqd_backup[i]); and amdgpu_gfx.c:373: kfree(adev->gfx.mec.mqd_backup[AMDGPU_MAX_COMPUTE_RINGS]); β€” neither is followed by mqd_backup[...] = NULL;.

Contrast the paired amdgpu_bo_free_kernel(&ring->mqd_obj, ...) which is safe to repeat because amdgpu_bo_free_kernel returns early on NULL (amdgpu_object.c:361) and amdgpu_bo_unref NULLs *bo.

Threat model

Speculative. amdgpu_device_ip sw_fini for the GFX block is invoked once during driver unload / device destroy; no error-retry or reset path re-enters amdgpu_gfx_compute_mqd_sw_fini on the same adev was found.

If such a path existed, the result would be a heap double-free β†’ potential local privilege escalation via slab grooming, but no such path is demonstrated. Filed for hardening because the asymmetry (BO free idempotent, kfree not) is a genuine footgun.

NULL each slot after kfree so the function becomes idempotent, matching the BO-free behavior:

--- a/sys/dev/drm/amd/amdgpu/amdgpu_gfx.c
+++ b/sys/dev/drm/amd/amdgpu/amdgpu_gfx.c
@@ -363,8 +363,11 @@ void amdgpu_gfx_compute_mqd_sw_fini(struct amdgpu_device *adev)
        amdgpu_bo_free_kernel(&ring->mqd_obj,
                      (u64 *)&ring->mqd_gpu_addr,
                      &ring->mqd_ptr);
+       adev->gfx.mec.mqd_backup[i] = NULL;
    }

    ring = &adev->gfx.kiq.ring;
    kfree(adev->gfx.mec.mqd_backup[AMDGPU_MAX_COMPUTE_RINGS]);
+   adev->gfx.mec.mqd_backup[AMDGPU_MAX_COMPUTE_RINGS] = NULL;
    amdgpu_bo_free_kernel(&ring->mqd_obj,

References

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1999 Β· 3 files
FileTypeDescriptionSize
VERDICT.md verdict source-only confirmation + mechanism + fix 1.5 KB ↓ raw
fix.diff suggested-fix After kfree: adev->gfx.mec.mqd_backup[i] = NULL; 693 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-1999 β€” PoC Verification Verdict

Category: drm (module / HW-gated) Source: sys/dev/drm/amd/amdgpu/amdgpu_gfx.c:366-373 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_compute_mqd_sw_fini: kfree(adev->gfx.mec.mqd_backup[i]) in a loop, no NULL assignment. If fini called twice (error recovery / module reload race) the slots are double-freed.

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

After kfree: adev->gfx.mec.mqd_backup[i] = NULL;

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_compute_mqd_sw_fini kfrees adev->gfx.mec.mqd_backup[i] in loop without NULL assignment; if fini called twice (error recovery / module reload race) the slots are do

Verified recommended fix

REPRODUCED (source-only): amdgpu_gfx_compute_mqd_sw_fini kfrees adev->gfx.mec.mqd_backup[i] in loop without NULL assignment; if fini called twice (error recovery / module reload race) the slots are double-freed.

Verdict

REPRODUCED (source-only): amdgpu_gfx_compute_mqd_sw_fini kfrees adev->gfx.mec.mqd_backup[i] in loop without NULL assignment; if fini called twice (error recovery / module reload race) the slots are double-freed.