DF-1781 / harness.c
/* * DF-1781 - drm_crtc.c uninitialized heap read in drm_mode_setcrtc * cleanup derefs garbage connector pointers. * * Vulnerable code (sys/dev/drm/drm_crtc.c): * 713 connector_set = kmalloc_array(count_connectors, sizeof(*), GFP_KERNEL); * // NO M_ZERO. linuxkpi slab.h kmalloc_array -> kmalloc, no zero. * 721 for (i = 0; i < count_connectors; i++) { * 722 connector_set[i] = NULL; // only THIS slot zeroed * 724 if (get_user(out_id, ...)) { ret=-EFAULT; goto out; } // bail * ... * 761 out: for (i = 0; i < count_connectors; i++) { * 763 if (connector_set[i]) // reads uninit slots * 764 drm_connector_put(connector_set[i]); // derefs garbage * * If iteration k fails (EFAULT from get_user or unknown connector id), * slots k+1..count_connectors-1 were never touched and still hold heap * residue. The cleanup loop reads them all; non-NULL garbage causes * drm_connector_put(garbage) -> drm_mode_object_put(&garbage->base) -> * kref_put on a wild refcount pointer. * * Trigger: local DRM master (logind/consolekit or SET_MASTER ioctl). * * This harness reproduces the uninit-slot deref logic. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define GFP_KERNEL 0 void *kmalloc_array_no_zero(size_t n, size_t sz) { return malloc(n * sz); } void *kcalloc_zero(size_t n, size_t sz) { return calloc(n, sz); } struct drm_connector { uint32_t base_id; int refcount; }; int main(void) { int count = 4; printf("=== DF-1781 drm_mode_setcrtc uninit connector_set harness ===\n"); printf("crtc_req->count_connectors = %d\n", count); /* buggy path: kmalloc_array WITHOUT M_ZERO */ struct drm_connector **cs = kmalloc_array_no_zero(count, sizeof(*cs)); /* simulate heap residue so uninit slots are non-NULL */ memset(cs, 0xAA, count * sizeof(*cs)); int fail_at = 1; /* iteration 1 fails with -EFAULT */ printf("loop fails at i=%d (EFAULT/ENOENT)\n", fail_at); printf("slots [%d..%d] never touched -> still heap residue\n", fail_at+1, count-1); for (int i = 0; i < count; i++) { cs[i] = NULL; /* drm_crtc.c:722 zero only this slot */ if (i == fail_at) { printf(" i=%d: bail to out: cs[%d]=NULL set\n", i, i); break; } cs[i] = &(struct drm_connector){i*10+1, 1}; } /* cleanup loop reads ALL count_connectors slots (drm_crtc.c:761-766) */ int uninit_derefs = 0; for (int i = 0; i < count; i++) { if (cs[i]) { struct drm_connector *garbage = cs[i]; printf(" cleanup[%d]: cs[i]=%p non-NULL -> drm_connector_put" " derefs base->refcount at %p (WILD)\n", i, (void*)garbage, (void*)&garbage->refcount); uninit_derefs++; } else { printf(" cleanup[%d]: cs[i]=NULL -> skipped\n", i); } } printf("\n"); if (uninit_derefs > 0) { printf("VERDICT: BUG CONFIRMED. kmalloc_array without M_ZERO leaves\n" " slots past the failure index as heap residue. The\n" " cleanup loop at drm_crtc.c:761-766 reads every slot\n" " and calls drm_connector_put on non-NULL garbage,\n" " dereferencing wild pointers. Fix: kcalloc / M_ZERO.\n"); free(cs); return 0; } printf("VERDICT: not reproduced.\n"); free(cs); return 1; } |