drm_crtc: uninitialized heap read in drm_mode_setcrtc cleanup derefs garbage connector pointers
Summary
drm_mode_setcrtc at 713 allocates connector_set[] via kmalloc_array NO M_ZERO (linuxkpi slab.h:47-50 returns kmalloc no M_ZERO unlike kcalloc). Populate loop 721-741 sets connector_set[i]=NULL only at START of current iteration i. Error partway (get_user EFAULT at 724 or drm_connector_lookup NULL at 730) leaves entries [k+1..count_connectors-1] uninitialized heap. Cleanup loop 761-766 iterates ALL count_connectors; guard if(connector_set[i]) reads uninit slots; non-NULL -> drm_connector_put(garbage) -> drm_mode_object_put(&garbage->base) -> kref_put on garbage refcount -> deref garbage kernel address. Local DRM master (logind/consolekit or SET_MASTER). Panic DoS or with slab grooming UAF chain. Fix: kcalloc instead of kmalloc_array.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1781 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace harness that reproduces the bug logic | 3.4 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -Wextra -o harness harness.c | 98 B | view raw |
| run.sh | run-script | ./harness | 59 B | view raw |
| build.log | build-log | full build output | 13 B | view raw |
| run.log | run-log | full decisive run output | 919 B | view raw |
| env.txt | environment | uname + cc version | 188 B | view raw |
| VERDICT.md | verdict | full narrative: mechanism, Phase 6, fix | 1.9 KB | β raw |
| fix.diff | suggested-fix | git-apply-able one-logical-change fix | 460 B | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1781 β drm_crtc.c uninitialized heap read in drm_mode_setcrtc cleanup
Verdict
REPRODUCED (logic/harness) β bug confirmed by source trace. Reach
requires a DRM master (logind / consolekit grant or DRM_IOCTL_SET_MASTER).
The default guest has no DRM device, so no live trigger.
Mechanism (path:line)
sys/dev/drm/drm_crtc.c:713-715βconnector_set = kmalloc_array(crtc_req->count_connectors, sizeof(struct drm_connector *), GFP_KERNEL);βkmalloc_arrayin the linuxkpi slab shim (sys/dev/drm/include/linux/slab.h) does not passM_ZERO, unlike Linux'skcalloc.sys/dev/drm/drm_crtc.c:721-741β populate loop setsconnector_set[i] = NULL;only at the start of iterationi. If an iteration fails (get_userEFAULT at 724 or unknown connector at 730), control jumps toout:, leaving slots[k+1..count_connectors-1]as heap residue.sys/dev/drm/drm_crtc.c:761-766β cleanup loop iterates allcount_connectorsslots:if (connector_set[i]) drm_connector_put(connector_set[i]);β reads uninit slots; non-NULL garbage callsdrm_connector_put(garbage)βdrm_mode_object_put(&garbage->base)βkref_puton a wild refcount pointer.
Phase 6 escalation
DRM-master reach. Panic-DoS is the immediate effect (wild deref); with slab grooming the wild pointer can be a UAF re-claim into a victim object β uid0. Not developed because the default guest has no DRM device.
PoC
harness.c simulates the populate loop failing at iteration 1 of a
4-slot connector_set. With memset(cs, 0xAA, ...) to mimic heap
residue, slots [2..3] are non-NULL and the cleanup loop dereferences
them as wild pointers.
Fix
fix.diff switches kmalloc_array to kcalloc (sys/dev/drm/include/linux/slab.h:44 defines kcalloc as kzalloc(n*size, flags) which zeroes). One-line change at drm_crtc.c:713. Validated by a clean drm.ko rebuild with the patch applied.
Fix verification
fixedVALIDATED at module-build level: applied fix.diff to drm_crtc.c, 'make' rc=0, drm.ko links cleanly with the kcalloc change.
baseline: harness shows cleanup[2]/[3] read 0xAA... residue and deref wild pointers patched: drm.ko builds clean; drm_crtc.c:713 now uses kcalloc which zero-initializes the array, so all slots are NULL until explicitly populated -> no uninit read.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- d
- r
- m
- _
- c
- r
- t
- c
- .
- c
- :
- 7
- 1
- 3
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- d
- r
- m
- _
- c
- r
- t
- c
- .
- c
- :
- 7
- 2
- 1
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- d
- r
- m
- _
- c
- r
- t
- c
- .
- c
- :
- 7
- 6
- 1
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- d
- r
- m
- _
- c
- r
- t
- c
- .
- c
- :
- 7
- 6
- 3
Detail
Exploit chain
DRM-master reach (logind/consolekit grant or DRM_IOCTL_SET_MASTER). Immediate effect is panic-DoS (wild deref); with slab grooming the wild pointer can be a UAF re-claim into a victim object -> uid0 on this guest. Not developed because the default guest has no DRM device. Harness in harness.c.
Evidence (decisive lines)
loop fails at i=1 (EFAULT/ENOENT) slots [2..3] never touched -> still heap residue cleanup[2]: cs[i]=0xaaaaaaaaaaaaaaaa non-NULL -> drm_connector_put derefs base->refcount at 0xaaaaaaaaaaaaaaac (WILD) VERDICT: BUG CONFIRMED. kmalloc_array without M_ZERO leaves slots past the failure index as heap residue.
PoC changes
Wrote harness.c, build.sh, run.sh, VERDICT.md, manifest.json, fix.diff. Original folder was empty.
Verified recommended fix
fix.diff switches kmalloc_array to kcalloc (slab.h:44 defines kcalloc as kzalloc(n*size, flags) which zeroes). One-line change at drm_crtc.c:713.
Verdict
REPRODUCED (logic/harness). drm_crtc.c:713 connector_set = kmalloc_array(count_connectors, sizeof(*), GFP_KERNEL) - linuxkpi slab.h kmalloc_array returns kmalloc with NO M_ZERO (unlike kcalloc). Populate loop at 721-741 sets connector_set[i]=NULL only at the start of iteration i. On mid-loop failure (EFAULT at 724 or unknown connector at 730) slots k+1..count-1 stay as heap residue. Cleanup loop at 761-766 iterates ALL count_connectors slots and calls drm_connector_put(connector_set[i]) on non-NULL garbage -> drm_mode_object_put(&garbage->base) -> kref_put on a wild refcount pointer. Harness demonstrates with count=4, fail_at=1, memset(cs,0xAA,...) to mimic residue.
No comments yet.