Duplicate connector IDs in DRM_IOCTL_MODE_SETCRTC leak one drm_connector reference per duplicate slot per successful modeset β per-slot get vs per-connector binding, surplus dropped only on the fail path
Summary
SETCRTC copies user connector IDs without de-dup; the get loop takes one ref per ARRAY SLOT for unbound connectors, but the dev-list traversal establishes exactly one binding per connector and the surplus put loop exists only under fail:. An unbound connector listed k>=2 times leaks k-1 refs per successful call; once destroyed (MST unplug) its refcount never reaches zero. All 8 combinations traced - only unbound-dup-success is imbalanced. Reject-duplicates-at-ioctl fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4470 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| setcrtc_dup.c | β | 8.3 KB | view raw | |
| build.sh | β | 89 B | view raw | |
| run.sh | β | 118 B | view raw | |
| build.log | β | 100 B | view raw | |
| run.log | β | 188 B | view raw | |
| env.txt | β | 411 B | view raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| fix.diff | β | 656 B | view raw |
DF-4470 VERDICT β duplicate connector IDs leak one reference per successful modeset
Status: INCONCLUSIVE (arithmetic certain; guest lacks DRM device)
Root cause (path:line)
sys/dev/drm/drm_crtc.c:721-741βdrm_mode_setcrtccopies user connector IDs intoconnector_set[]with per-ID lookup and no de-duplication;set->num_connectors = crtc_req->count_connectors(line 749).sys/dev/drm/drm_crtc_helper.c:626-630c for (ro = 0; ro < set->num_connectors; ro++) { if (set->connectors[ro]->encoder) continue; drm_connector_get(set->connectors[ro]); /* once per SLOT */ }- Binding happens once per (dev-list) connector β not per array slot β at
drm_crtc_helper.c:635-666(connector->encoder = new_encoder). The reference protocol is "one reference while the encoder is bound" (comment at drm_crtc_helper.c:468, drop at drm_crtc_helper.c:469). - The surplus drop exists ONLY on the failure path
(
drm_crtc_helper.c:775-779); the success return at lines 755-757 has no put loop.
β an unbound connector listed kβ₯2 times in set_connectors receives k gets
and 1 binding: +(kβ1) leaked references per successful setcrtc. A bound
connector duplicated is balanced (get skipped, put skipped).
Impact: the kref on drm_connector never again reaches 0 after the connector
is destroyed (DP MST unplug path, drm_connector_unregister+cleanup) β the
connector object (plus its name string, property blobs) is leaked
permanently. Bounded-per-call (no per-reference allocation), unbounded in
call count; requires DRM master (local unprivileged user on a headless
system). Upstream v4.15-identical; Linux master still carries the same
get/put asymmetry (checked 2026-09).
The fail path is balanced for duplicates: k gets (step 2) vs k puts (lines 775-779 iterate the array), verified by tracing all four bound/unbound Γ dup/no-dup Γ success/fail combinations β only unbound-dup-success leaks.
Fix
De-duplicate in drm_mode_setcrtc (drm_crtc.c:721-741):
--- a/sys/dev/drm/drm_crtc.c
+++ b/sys/dev/drm/drm_crtc.c
@@ -729,6 +729,11 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
ret = -ENOENT;
goto out;
}
+ for (j = 0; j < i; j++) {
+ if (connector_set[j] == connector) {
+ ret = -EINVAL; /* duplicate connector id */
+ goto out;
+ }
+ }
DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n",
connector->base.id,
connector->name);
(and declare int j;) β or make the get loop in drm_crtc_helper.c idempotent
per connector.
Attempts
- Guest build: build.log (same binary as DF-4469).
- Guest run: no /dev/dri β run.log (ENOENT).
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 on guest (no DRM device); fix.diff rejects duplicate connector ids at drm_mode_setcrtc with -EINVAL before any reference is taken
findings/poc/DF-4470/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (get/put arithmetic over all combinations), setcrtc_dup.c (dup-mode harness), build.log/run.log (guest ENOENT), fix.diff (de-dup in drm_mode_setcrtc)
PoC changes
shared harness with DF-4469; dup variant added full fb+mode setup (disable-only setcrtc never reaches the get loop)
Verified recommended fix
Reject duplicate connector IDs in drm_mode_setcrtc (drm_crtc.c) or make the get loop idempotent per connector.
Verdict
drm_mode_setcrtc copies user connector IDs without de-duplication (drm_crtc.c:721-741); drm_crtc_helper_set_config takes one drm_connector_get() per ARRAY SLOT for unbound connectors (drm_crtc_helper.c:626-630) but establishes exactly one binding per connector (drm_crtc_helper.c:657-666) and the surplus-drop loop exists only on the fail path (drm_crtc_helper.c:775-779) - the success return (755-757) never drops it. An unbound connector listed k>=2 times therefore leaks k-1 references per successful modeset; after the connector is destroyed (MST unplug) its refcount never reaches zero and the object leaks permanently. All 8 bound/unbound x dup/no-dup x success/fail combinations traced - only unbound-dup-success is imbalanced. Not reproducible on the audit guest (no DRM device; /dev/dri absent - open returns ENOENT); arithmetic is fully code-proven. Upstream v4.15-identical and still present in current Linux.
No comments yet.