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.
Recommended fix
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
sys/dev/drm/amd/amdgpu/amdgpu_gfx.c:366,373βkfreewithout NULLsys/dev/drm/amd/amdgpu/amdgpu_object.c:361β pairedamdgpu_bo_free_kernelcorrectly NULL-guards
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1999 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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
fixedVALIDATED: 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.
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.
No comments yet.