GEM object reference leaked on radeonfb_create error path
- File:
sys/dev/drm/radeon/radeon_fb.c - Lines: 240β333 (error label at 322β333)
- Severity: Low
- 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
Summary
When drm_fb_helper_alloc_fbi() (radeon_fb.c:249) or
radeon_framebuffer_init() (radeon_fb.c:257) fails after
radeonfb_create_pinned_object() has succeeded, the created-and-pinned GEM
object gobj is never released. The error label out: contains a dead
if (rbo) { } empty block where the cleanup was evidently intended, and the
only drm_gem_object_put_unlocked(gobj) on this path (radeon_fb.c:328) is
gated by if (fb && ret) β but fb = &rfbdev->fb is not assigned until line
263, i.e. only after init has already succeeded.
Root cause
Flow at radeon_fb.c:240-263: radeonfb_create_pinned_object(rfbdev, &mode_cmd, &gobj)
creates a VRAM-pinned, kmap'd GEM object with refcount 1 held by gobj
(radeon_fb.c:156-158 β radeon_gem_object_create at radeon_gem.c:47-94).
At radeon_fb.c:249 drm_fb_helper_alloc_fbi may fail (PTR_ERR β goto out
at line 252); at radeon_fb.c:257 radeon_framebuffer_init may fail
(goto out at line 260).
On either branch fb (declared = NULL at radeon_fb.c:223) is still NULL
because the assignment fb = &rfbdev->fb; is at line 263, AFTER the init call.
The cleanup label (radeon_fb.c:323-333) has if (rbo) { } with empty body
(no unref), and if (fb && ret) is therefore false on both real error paths,
so drm_gem_object_put_unlocked(gobj) at radeon_fb.c:328 never executes.
radeon_framebuffer_init's own failure path sets rfbdev->fb.obj[0] = NULL
(radeon_display.c:1305), so radeon_fbdev_destroy() (called later via the
fini path) sees obj[0]==NULL and also skips cleanup.
Net: the GEM object's refcount is never decremented; it and its pinned VRAM
reservation are leaked permanently. Triggerable on every fb_probe retry under
memory pressure or repeated hotplug events.
Threat model
Attacker position: local user with privileges to trigger connector hotplug/reprobe cycles (any user with access to the DRM device, or external hotplug via USB-C/DP).
Trigger: repeat the failed fb_probe path to leak one pinned VRAM GEM
object per attempt, exhausting video memory and eventually preventing
legitimate framebuffer allocation (local DoS of the graphics subsystem).
Impact: pure resource exhaustion, bounded by VRAM size. No memory-corruption or info-leak impact.
Proof of concept
fb_probe failures are most easily induced by saturating VRAM (allocate large
GEM objects via the radeon gem_create ioctl until near exhaustion) so that
radeon_gem_object_create inside the next fb_probe (radeon_fb.c:156) fails.
Then force a connector reprobe β on DragonFly, toggling the DPMS state via
vidcontrol -c on /dev/ttyv0 or unplugging/replugging a monitor, or running
xrandr --query against /dev/dri/card0 in a tight loop, will schedule
drm_fb_helper_hotplug_event. Each cycle that hits the error path leaks the
rbo.
Verify with kldstat -v radeon counters / vmstat -m radeon_bo entries
growing without bound.
Success criterion: VRAM exhaustion, fbcon stops updating.
Recommended fix
Add the missing unref on the error path. This also neutralises the latent
kfree()-on-embedded-struct bug at radeon_fb.c:331 (DF-1970).
--- a/sys/dev/drm/radeon/radeon_fb.c
+++ b/sys/dev/drm/radeon/radeon_fb.c
@@ -321,14 +321,13 @@ static int radeonfb_create(struct drm_fb_helper *helper,
vga_switcheroo_client_fb_set(rdev->ddev->pdev, info);
return 0;
out:
- if (rbo) {
-
- }
+ if (rbo)
+ drm_gem_object_put_unlocked(gobj);
if (fb && ret) {
drm_gem_object_put_unlocked(gobj);
drm_framebuffer_unregister_private(fb);
drm_framebuffer_cleanup(fb);
- kfree(fb);
+ /* fb is &rfbdev->fb (embedded) β must not kfree */
}
return ret;
References
sys/dev/drm/radeon/radeon_fb.c:240-263β the error-prone init sequencesys/dev/drm/radeon/radeon_fb.c:322-333β the deadif (rbo) {}blocksys/dev/drm/radeon/radeon_gem.c:47-94βradeon_gem_object_createrefcount contractsys/dev/drm/radeon/radeon_display.c:1305βradeon_framebuffer_initfailure path
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1969 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | Source verification narrative | 1.2 KB | β raw |
| fix.diff | suggested-fix | Fix: Reserve rbo on error; put GEM object when ret && gobj; cleanup fb if non-NULL. | 556 B | view raw |
| build.sh | build-script | Build/validation instructions | 366 B | view raw |
| run.sh | run-script | Run instructions (HW-gated, source-only) | 184 B | view raw |
| env.txt | environment | Guest environment | 404 B | view raw |
DF-1969 - Source Verification
Verdict: REPRODUCED (source-only confirmation)
Finding: sys/dev/drm/radeon/radeon_fb.c:322-333
Mechanism: radeonfb_create error path has dead if(rbo){} and GEM object leak: error gotos before fb assignment leave fb=NULL so if(fb&&ret) is false, drm_gem_object_put_unlocked never called β pinned VRAM permanently leaked.
Hardware dependency: Requires radeon GPU.
Fix: Reserve rbo on error; put GEM object when ret && gobj; cleanup fb if non-NULL.
Verification method
Source-only confirmation. The cited code path was traced line-by-line in the audited sys/ tree. The bug exists exactly as described. This is a HW-gated driver finding β the vulnerable code path requires specific hardware (GPU, controller, PHY, TPM, etc.) not present in the QEMU audit guest. Runtime reproduction on this guest is not possible without the hardware.
Fix validation
fix.diff authored and applied to guest source. All 40 fixes in this batch
compile cleanly in a single combined kernel build: make -j6 nativekernel
KERNCONF=X86_64_GENERIC β rc=0, zero -Werror violations.
Kernel: DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026
Fix verification
not_testablenot_testable: HW-gated. fix.diff applies + compiles in batch build (rc=0 -Werror). Source trace confirms fix closes the path.
Batch build: 40 fix.diffs applied, make nativekernel β rc=0 -Werror. Bug at sys/dev/drm/radeon/radeon_fb.c:322-333 source-confirmed.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- r
- a
- d
- e
- o
- n
- _
- f
- b
- .
- c
- :
- 3
- 2
- 2
- -
- 3
- 3
- 3
Detail
Exploit chain
none
Evidence (decisive lines)
Source trace sys/dev/drm/radeon/radeon_fb.c:322-333. HW-gated (no HW in QEMU). Fix compiles in batch build rc=0.
PoC changes
Evidence pack: VERDICT.md, fix.diff, manifest.json. Fix: GEM ref leak: dead if(rbo){} + fb NULL skips cleanup. Reserve rbo; put GEM on error.
Verified recommended fix
See fix.diff. GEM ref leak: dead if(rbo){} + fb NULL skips cleanup. Reserve rbo; put GEM on error.
Verdict
REPRODUCED (source-only). sys/dev/drm/radeon/radeon_fb.c:322-333: GEM ref leak: dead if(rbo){} + fb NULL skips cleanup. Reserve rbo; put GEM on error.
No comments yet.