β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-1781

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1781 Β· 10 files
FileTypeDescriptionSize
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
VERDICT.md verdict full narrative: mechanism, Phase 6, fix
↓ download 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_array in the linuxkpi slab shim (sys/dev/drm/include/linux/slab.h) does not pass M_ZERO, unlike Linux's kcalloc.
  • sys/dev/drm/drm_crtc.c:721-741 β€” populate loop sets connector_set[i] = NULL; only at the start of iteration i. If an iteration fails (get_user EFAULT at 724 or unknown connector at 730), control jumps to out:, leaving slots [k+1..count_connectors-1] as heap residue.
  • sys/dev/drm/drm_crtc.c:761-766 β€” cleanup loop iterates all count_connectors slots: if (connector_set[i]) drm_connector_put(connector_set[i]); β€” reads uninit slots; non-NULL garbage calls drm_connector_put(garbage) β†’ drm_mode_object_put(&garbage->base) β†’ kref_put on 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

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED 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.
↓ fix.diffdrm.ko module rebuild (loadable .ko) - applied fix.diff, 'make' rc=0, drm.ko built clean

Confirmed kernel references

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.