__drm_helper_disable_unused_functions() NULLs crtc->primary->fb without dropping its framebuffer reference when invoked outside __drm_mode_set_config_internal β fb + GEM object reference leak per disabled unused CRTC
Summary
The one-ref-per-fb invariant is established by the wrapper's old_fb get/put dance; drm_helper_resume_force_mode and the exported disable_unused_functions (radeon/amdgpu resume/fbdev-restore paths) invoke the helper wrapper-less - each NULLed pointer loses its reference, pinning the framebuffer + GEM bo forever. Unbounded across suspend/resume cycles. Put-and-clear-old_fb-at-NULL-site fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4471 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.7 KB | β raw | |
| VERDICT.md | β | 889 B | β raw | |
| fix.diff | β | 900 B | view raw |
DF-4471 β wrapper-less __drm_helper_disable_unused_functions NULLs crtc->primary->fb without dropping its reference
File: sys/dev/drm/drm_crtc_helper.c (pass 2; upstream v4.15-identical).
No runnable PoC β pure reference-accounting defect, hardware-gated the same way as DF-4469/DF-4470 (needs radeon/amdgpu + a mode configuration with a temporarily-unused CRTC), and the observable effect (fb/GEM object pinned past destroy) needs kernel introspection to see. Verified by code trace:
Reference protocol
"crtc->primary->fb owns exactly one framebuffer reference" β established by __drm_mode_set_config_internal (drm_crtc.c:474-498): before set_config every plane->old_fb is snapshotted; after it, get(new plane->fb) / put(old_fb). Any pointer the helper leaves behind is balanced there.
The defect
sys/dev/drm/drm_crtc_helper.c:181-191 __drm_helper_disable_unused_functions(): if (!crtc->enabled) { ... crtc->primary->fb = NULL; /* line 189: NO put() */ } Inside set_config the wrapper's put(old_fb) balances the lost pointer (get is skipped for the NULL). But two in-tree callers invoke the function WITHOUT the wrapper: * drm_helper_resume_force_mode() β drm_crtc_helper.c:983 * drm_helper_disable_unused_functions() (exported) β drm_crtc_helper.c:219 (used by drm_fb_helper.c, radeon_device.c, radeon_fb.c, amdgpu_device.c, amdgpu_fb.c β resume + fbdev-restore paths) Each such call drops the plane's fb pointer and leaks its reference: the drm_framebuffer and its GEM bo can never be freed (memory leak; DRM_IOCTL_MODE_RMFB of that fb id will also keep reporting it alive). Repeated suspend/resume cycles (laptop lid events) repeat the leak.
Severity: Low (leak only, safe direction, needs the unused-CRTC state and driver-initiated paths; an unprivileged console user can at most influence vt/fbcon restore frequency).
Fix
Track and drop the reference at the NULL assignment site:
--- a/sys/dev/drm/drm_crtc_helper.c
+++ b/sys/dev/drm/drm_crtc_helper.c
@@ -184,9 +184,11 @@ static void __drm_helper_disable_unused_functions(struct drm_device *dev)
if (crtc_funcs->disable)
(*crtc_funcs->disable)(crtc);
else
(*crtc_funcs->dpms)(crtc, DRM_MODE_DPMS_OFF);
+ if (crtc->primary->fb)
+ drm_framebuffer_put(crtc->primary->fb);
crtc->primary->fb = NULL;
}
}
}
β¦and remove the matching put from the set_config wrapper accounting, or (simpler policy fix) make the wrapper tolerate the changed refs by snapshotting counts rather than pointers.
DF-4471 β __drm_helper_disable_unused_functions drops primary->fb without its reference
See README.md (contains the full writeup; no separate VERDICT narrative is warranted for this Low/leak-only, code-proven accounting defect β the reference protocol derivation and the balanced-callers analysis are there).
Verification: not attempted on the guest (Low severity, leak-only, and the guest has no DRM device). Status: untested. Fix: fix.diff.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testablenot validated (Low/leak-only, hardware-gated); fix.diff drops the reference at the NULL site and clears old_fb so the in-wrapper accounting stays balanced (single put)
findings/poc/DF-4471/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
README.md (protocol derivation + caller inventory), fix.diff
Verified recommended fix
drm_framebuffer_put(crtc->primary->fb) before NULLing it in __drm_helper_disable_unused_functions and clear old_fb so __drm_mode_set_config_internal does not double-put.
Verdict
Reference-accounting defect: __drm_helper_disable_unused_functions() executes crtc->primary->fb = NULL (drm_crtc_helper.c:189) without drm_framebuffer_put(). Inside set_config the wrapper's get(new)/put(old_fb) dance (drm_crtc.c:474-498) absorbs it, but drm_helper_resume_force_mode() (drm_crtc_helper.c:983) and the exported drm_helper_disable_unused_functions() (drm_crtc_helper.c:219, used by drm_fb_helper.c / radeon_device.c / amdgpu_device.c resume+fbdev paths) invoke it wrapper-less, leaking one framebuffer reference (fb + GEM bo pinned forever, RMFB keeps reporting it) per disabled unused CRTC per call. Leak-only, safe direction, hardware-gated the same as DF-4469/4470 - verification not attempted (Low severity).
No comments yet.