DF-1011 / uvc_nitv_harness.c
/* * 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; } |