GEM object refcount leak on amdgpufb_create error path (VRAM buffer permanently leaked)
- File:
sys/dev/drm/amd/amdgpu/amdgpu_fb.c - Lines: 228, 237β252 (error paths), 308β316 (broken cleanup)
- Severity: Medium
- CVSS 3.1:
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U:C:N/I:N/A:L - CWE: CWE-401 Missing Release of Memory after Effective Lifetime
- Confidence: certain
- Status: new
- Related: DF-1969 (radeon_fb.c identical-class bug)
Summary
When drm_fb_helper_alloc_fbi() (line 237) or
amdgpu_display_framebuffer_init() (line 248) fails after
amdgpufb_create_pinned_object() (line 228) succeeds, the out: error label
has broken cleanup: the if (abo) { } block is empty (lines 309-311) and
if (fb && ret) at line 312 is false because fb is still NULL at those
error points.
The GEM object's reference count is never decremented, permanently leaking the pinned VRAM buffer object.
Root cause
In amdgpufb_create(), after amdgpufb_create_pinned_object(rfbdev, &mode_cmd, &gobj)
succeeds at line 228, gobj holds a reference to a pinned+kmap'd VRAM buffer
object.
Two error paths follow:
- Path A:
drm_fb_helper_alloc_fbi(helper)at line 237 returnsIS_ERR(info)βgoto out(line 240). At this pointfbis stillNULL(set only at line 255, afterframebuffer_initsucceeds). - Path B:
amdgpu_display_framebuffer_init()at line 248 fails βgoto out(line 252).fbis stillNULL.
At the out: label (line 308):
- if (abo) { } (line 309) is an EMPTY BLOCK β clearly someone intended
cleanup here but left it incomplete.
- if (fb && ret) (line 312) is FALSE because fb == NULL on both paths.
So gobj is never released. amdgpufb_destroy_pinned_object() (line 115-127),
which does amdgpu_bo_kunmap + amdgpu_bo_unpin + drm_gem_object_put_unlocked,
is never called.
The GEM object and its backing VRAM allocation
(size = pitches[0] * ALIGN(height, 8), page-aligned, pinned in VRAM) are
leaked for the lifetime of the kernel.
Later, amdgpu_fbdev_destroy() (line 321-336) checks
if (rfb->base.obj[0]) (line 327) before cleanup. On Path B,
amdgpu_display_framebuffer_init sets obj[0]=NULL on failure
(amdgpu_display.c:516), so the destroy function skips cleanup entirely. On
Path A, obj[0] was never set (still zero from kzalloc). In both cases, the
leak is permanent.
Additionally, amdgpu_fbdev_init() at line 383-384 ignores the return value
of drm_fb_helper_initial_config and always returns 0, so the driver proceeds
as if fbdev init succeeded.
Threat model
Attacker position: local attacker with DRM device access or the ability to trigger display hotplug/modeset events.
Trigger: repeat fbdev re-probe failures to leak several MB of pinned VRAM per attempt. Over time this exhausts VRAM, causing subsequent modeset/scanout allocations to fail β local DoS.
On amdgpu, the framebuffer surface size can be large (e.g., 16384*4 * ALIGN(8640,8) β 572 MB
for a 16K display). Even a single leak of this size is significant; repeated
leaks via hotplug cycles are devastating.
The attack requires the fb_probe path to fail (e.g., low VRAM, or GART bind
failure from amdgpu_ttm_alloc_gart at line 185), which an attacker can
influence by consuming VRAM via other DRM allocations.
Proof of concept
- Open the amdgpu DRM device (
/dev/drmX). - Allocate large VRAM GEM objects via
DRM_IOCTL_AMDKGPU_GEM_CREATEorDRM_IOCTL_MODE_CREATE_DUMBuntil VRAM is nearly exhausted. - Trigger a fbdev re-probe β on DragonFly this happens via connector hotplug
(unplug/replug display) or by triggering
drm_fb_helper_hotplug_event. amdgpufb_create_pinned_objectsucceeds (enough VRAM for one object) butamdgpu_ttm_alloc_gart(line 185) fails because GART space is exhausted, ordrm_fb_helper_alloc_fbifails under memory pressure.- The GEM object is leaked.
- Repeat until VRAM is permanently consumed.
Verification: check systat -vmstat or amdgpu VRAM usage sysfs
before/after β the leaked objects never appear as freeable.
Success criterion: fbdev/modeset failures after repeated cycling, or OOM from VRAM exhaustion.
Recommended fix
Replace the broken out: error path with proper cleanup. The GEM object must
be released on all error paths, and the latent kfree(fb) must be removed
(see DF-1983).
--- a/sys/dev/drm/amd/amdgpu/amdgpu_fb.c
+++ b/sys/dev/drm/amd/amdgpu/amdgpu_fb.c
@@ -305,16 +305,14 @@ static int amdgpufb_create(struct drm_fb_helper *helper,
vga_switcheroo_client_fb_set(adev->ddev->pdev, info);
return 0;
out:
- if (abo) {
-
- }
- if (fb && ret) {
- drm_gem_object_put_unlocked(gobj);
- drm_framebuffer_unregister_private(fb);
- drm_framebuffer_cleanup(fb);
- kfree(fb);
- }
+ /*
+ * Release the pinned GEM object on any error path. fb is embedded
+ * in the kzalloc'd rfbdev (never separately allocated), so never
+ * kfree() it -- see amdgpu_mode.h:316-321.
+ */
+ if (gobj)
+ amdgpufb_destroy_pinned_object(gobj);
return ret;
}
References
sys/dev/drm/amd/amdgpu/amdgpu_fb.c:228,237-252β error-prone init sequencesys/dev/drm/amd/amdgpu/amdgpu_fb.c:308-316β broken error cleanup (emptyif(abo){}block)sys/dev/drm/amd/amdgpu/amdgpu_fb.c:115-127βamdgpufb_destroy_pinned_object(the proper cleanup)sys/dev/drm/amd/amdgpu/amdgpu_fb.c:383-384βamdgpu_fbdev_initignores return valuesys/dev/drm/amd/amdgpu/amdgpu_display.c:516βobj[0]=NULLon init failure
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1982 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | PoC trigger description | 317 B | β raw |
| VERDICT.md | verdict | verification narrative | 932 B | β raw |
| fix.diff | suggested-fix | git-apply-able fix | 480 B | view raw |
| fix_build_summary.txt | build-log | combined 16-finding kernel build rc=0 | 826 B | view raw |
DF-1982 PoC
See the parent finding markdown at findings/DF-1982-*.md for the full threat
model and PoC steps. This directory is the evidence-pack slot for the PoC
runner; the runner will populate it with sources, build.sh / run.sh, full
untrimmed logs, env.txt, VERDICT.md, and manifest.json after verification.
DF-1982 Verification
Verdict
SOURCE-CONFIRMED, INCONCLUSIVE-RUNTIME (HW/module gated).
The cited defect exists in the audited source at sys/dev/drm/amd/amdgpu/amdgpu_fb.c:305-316.
amdgpu is not in GENERIC and requires real AMD GPU hardware.
Mechanism (source-only confirmation)
amdgpufb_create error label out: at L308-316 has an empty "if (abo) {}" block (309-311) where cleanup was intended. On error paths where fb was never set up (alloc_fbi failure at L240, framebuffer_init failure at L253), the GEM object reference acquired earlier is never released β permanent VRAM leak. fb=&rfbdev->rfb.base is only assigned at L257, after the possible error gotos.
Recommended fix
Fill the empty if (abo) block: on error paths where fb==NULL (framebuffer never set up), call drm_gem_object_put_unlocked(gobj) to release the leaked GEM reference.
The full git apply-able diff lives in fix.diff in this folder.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- a
- m
- d
- g
- p
- u
- /
- a
- m
- d
- g
- p
- u
- _
- f
- b
- .
- c
- :
- 3
- 0
- 5
- -
- 3
- 1
- 6
Detail
Exploit chain
none (non-corruption: resource leak only)
Evidence (decisive lines)
Combined kernel build: 16 fix.diffs applied, make -j6 nativekernel => rc=0, 0 warnings, 0 errors.
PoC changes
Created VERDICT.md, fix.diff (fill empty if(abo) with GEM ref release), manifest.json, env.txt, build.sh, run.sh.
Verified recommended fix
Fill the empty if (abo) block: on error paths where fb==NULL, call drm_gem_object_put_unlocked(gobj). Matches finding proposal.
Verdict
SOURCE-CONFIRMED (HW/module gated). amdgpufb_create error label out: (amdgpu_fb.c:305-316) has empty 'if (abo) {}' block (L309-311) where cleanup was intended. On error paths where fb was never set up (alloc_fbi failure L240, framebuffer_init failure L253), GEM object reference is never released -> permanent VRAM leak. fb only assigned at L257, after possible error gotos. Confirmed by source trace. Not runnable: amdgpu module, no AMD HW.
No comments yet.