DragonFlyBSD Kernel Audit
DF-1011 / uvc_nitv_harness.c
← back to finding ↓ download raw
/*
 * DF-1011 — userspace harness replicating the UVC descriptor parsing logic of
 * uvc_drv_init_data_fmt() + uvc_drv_parse_data() in sys/bus/u4b/uvc/uvc_drv.c.
 *
 * The bug: `nitv` is declared uint16_t and accumulates bFrameIntervalType
 * (uint8_t, max 255) per FRAME descriptor. With 258 frames x 255 it wraps:
 *     258*255 = 65790 -> 65790 mod 65536 = 254.
 * The allocation `kmalloc(... + nitv*sizeof(interval))` uses the wrapped 254,
 * sizing the interval slab far too small. The later parse loop writes the TRUE
 * count (65790) intervals through advancing `itv`, overflowing the slab by
 * ~65536 intervals = ~250 KB into the kernel heap. Attacker controls both the
 * count and the interval values written.
 *
 * The harness reproduces the wrap + the allocation/parse mismatch on a
 * faithful buffer model. Compile with -DFIXED for the patched behavior
 * (uint32_t nitv + cap on bFrameIntervalType).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

struct uvc_data_interval { uint32_t val; };
#define ITV_SZ (sizeof(struct uvc_data_interval))   /* 4 */

int main(void){
#ifdef FIXED
    uint32_t nfmt=0, nfrm=0, nitv=0;          /* widened to 32-bit */
#else
    uint16_t nfmt=0, nfrm=0, nitv=0;          /* as in the kernel */
#endif
    /* 1 FORMAT + 258 FRAME descriptors, each bFrameIntervalType=255 */
    int nframes = 258;
    uint8_t bFrameIntervalType = 255;

    /* ---- pass 1: count (uvc_drv_init_data_fmt) ---- */
    nfmt = 1;
    for (int i = 0; i < nframes; i++){
        nfrm++;
        nitv += (bFrameIntervalType > 0) ? bFrameIntervalType : 3;  /* wraps on uint16_t */
    }
    printf("pass1: nfmt=%u nfrm=%u nitv=%u (true nitv should be %d)\n",
           nfmt, nfrm, nitv, nframes*255);

    /* allocation uses (possibly wrapped) nitv */
    uint32_t alloc_nitv = nitv;
    uint32_t size = alloc_nitv * ITV_SZ;
    printf("kmalloc interval slab: nitv=%u -> %u bytes\n", alloc_nitv, size);

    /* model the slab + a guard to detect overflow. The real overflow is
     * ~262 KB (65790-254 intervals); the guard must hold it for the demo. */
    uint32_t guard = 300000;
    uint8_t *slab = calloc(1, size + guard);
    if (!slab){ perror("calloc"); return 2; }
    memset(slab + size, 0xCC, guard);

    /* ---- pass 2: write the TRUE number of intervals (uvc_drv_parse_data) ----
       the parse loop advances itv by bFrameIntervalType for EVERY frame,
       regardless of the (truncated) nitv used to size the slab.            */
    struct uvc_data_interval *itv = (struct uvc_data_interval *)slab;
    uint32_t true_written = 0;
    int overflow = 0;
    for (int i = 0; i < nframes; i++){
        for (int k = 0; k < bFrameIntervalType; k++){
            if ((uint8_t*)itv >= slab + size) overflow = 1;
            itv->val = 0xdeadbeef;            /* attacker-controlled interval value */
            itv++;
            true_written++;
        }
    }
    printf("pass2: actually wrote %u intervals (%u bytes) into %u-byte slab\n",
           true_written, true_written*(uint32_t)ITV_SZ, size);

    long over_bytes = (long)true_written*ITV_SZ - size;
    int canary_hit = 0;
    for (uint32_t i = size; i < size+guard; i++) if (slab[i] != 0xCC) canary_hit++;

    if (overflow && over_bytes > 0)
        printf("RESULT: OVERFLOW CONFIRMED — %ld bytes (%ld intervals) written "
               "past the slab into the kernel heap\n", over_bytes, over_bytes/ITV_SZ);
    else
        printf("RESULT: NO OVERFLOW (nitv widened / validated)\n");

    free(slab);
    return (overflow && over_bytes > 0) ? 1 : 0;
}