DragonFlyBSD Kernel Audit
DF-1781 / harness.c
← back to finding ↓ download raw
/*
 * 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;
}