⬢ DragonFlyBSD Kernel Audit
DF-3783 / harness.c
← back to finding ↓ download raw
/*
 * DF-3783 -- BT848SCLIP clip-list -> RISC program builder overflow
 *
 * Source-level harness: verbatim port of the RISC-emission-relevant code from
 * sys/dev/video/bktr/bktr_core.c:
 *   notclipped()  bktr_core.c:2465-2490
 *   getline()     bktr_core.c:2492-2534
 *   split()       bktr_core.c:2536-2616   (Bpp fixed to 2 = default RGB16)
 *   rgb_prog()    bktr_core.c:2852-2957   (per-field emission loop + tail)
 *   BT848SCLIP validation  bktr_core.c:1219-1271
 *
 * The harness counts every dword (RISC instruction/address word) emitted into
 * bktr->dma_prog / bktr->odd_dma_prog and compares against DMA_PROG_ALLOC
 * (8 * PAGE_SIZE = 32768 bytes = 8192 dwords, bktr_core.c:141,395-396).
 *
 * The kernel buffer is a fixed 8-page contigmalloc() region; emitting more
 * than 8192 dwords is a linear kernel-heap overflow with RISC opcodes and
 * physical buffer addresses.
 */
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#define PAGE_SIZE		4096
#define DMA_PROG_ALLOC		(8 * PAGE_SIZE)
#define DMA_PROG_DWORDS		(DMA_PROG_ALLOC / 4)

#define BT848_MAX_CLIP_NODE 100
typedef struct { int x_min, x_max, y_min, y_max; } bktr_clip_t;
typedef unsigned char bool_t;
#define TRUE 1
#define FALSE 0

static int	min(int a, int b) { return a < b ? a : b; }

struct bktr_h {
	bktr_clip_t clip_list[BT848_MAX_CLIP_NODE];
	int max_clip_node;
	int clip_start, last_y, y, y2, yclip, yclip2, line_length, current_col;
};

/* opcode constants -- bktr_core.c:2433-2441 */
#define OP_WRITE      (0x1u << 28)
#define OP_SKIP       (0x2u << 28)
#define OP_WRITEC     (0x5u << 28)
#define OP_SOL	      (1u << 27)
#define OP_EOL	      (1u << 26)
#define DMA_BT848_SPLIT 319*2

/* ===================== verbatim: notclipped (2465-2490) ===================== */
static bool_t notclipped (struct bktr_h * bktr, int x, int width) {
    int i;
    bktr_clip_t * clip_node;
    bktr->clip_start = -1;
    bktr->last_y = 0;
    bktr->y = 0;
    bktr->y2 = width;
    bktr->line_length = width;
    bktr->yclip = -1;
    bktr->yclip2 = -1;
    bktr->current_col = 0;

    if (bktr->max_clip_node == 0 ) return TRUE;
    clip_node = (bktr_clip_t *) &bktr->clip_list[0];

    for (i = 0; i < bktr->max_clip_node; i++ ) {
	clip_node = (bktr_clip_t *) &bktr->clip_list[i];
	if (x >= clip_node->x_min && x <= clip_node->x_max  ) {
	    bktr->clip_start = i;
	    return FALSE;
	}
    }
    return TRUE;
}

/* ===================== verbatim: getline (2492-2534) ===================== */
static bool_t getline_h (struct bktr_h *bktr, int x ) {
    int i, j;
    bktr_clip_t * clip_node ;

    if (bktr->line_length == 0 ||
	bktr->current_col >= bktr->line_length) return FALSE;

    bktr->y = min(bktr->last_y, bktr->line_length);
    bktr->y2 = bktr->line_length;

    bktr->yclip = bktr->yclip2 = -1;
    for (i = bktr->clip_start; i < bktr->max_clip_node; i++ ) {
	clip_node = (bktr_clip_t *) &bktr->clip_list[i];
	if (x >= clip_node->x_min && x <= clip_node->x_max) {
	    if (bktr->last_y <= clip_node->y_min) {
		bktr->y =      min(bktr->last_y, bktr->line_length);
		bktr->y2 =     min(clip_node->y_min, bktr->line_length);
		bktr->yclip =  min(clip_node->y_min, bktr->line_length);
		bktr->yclip2 = min(clip_node->y_max, bktr->line_length);
		bktr->last_y = bktr->yclip2;
		bktr->clip_start = i;

		for (j = i+1; j  < bktr->max_clip_node; j++ ) {
		    clip_node = (bktr_clip_t *) &bktr->clip_list[j];
		    if (x >= clip_node->x_min && x <= clip_node->x_max) {
			if (bktr->last_y >= clip_node->y_min) {
			    bktr->yclip2 = min(clip_node->y_max, bktr->line_length);
			    bktr->last_y = bktr->yclip2;
			    bktr->clip_start = j;
			}
		    } else break  ;
		}
		return TRUE;
	    }
	}
    }

    if (bktr->current_col <= bktr->line_length) {
	bktr->current_col = bktr->line_length;
	return TRUE;
    }
    return FALSE;
}

/* ============ verbatim: split (2536-2616), counting only ============ */
static long emitted_dwords;

static bool_t split (struct bktr_h * bktr, int width,
		    unsigned long operation, int pixel_width,
		    unsigned long *target_buffer, int cols ) {

 unsigned long flag, flag2;
 unsigned int  skip, start_skip = 0;	/* Bpp==2 -> start_skip always 0 */

  if ((width * pixel_width) < DMA_BT848_SPLIT ) {
      if (  width == cols) {
	 flag = OP_SOL | OP_EOL;
       } else if (bktr->current_col == 0 ) {
	    flag  = OP_SOL;
       } else if (bktr->current_col == cols) {
	    flag = OP_EOL;
       } else flag = 0;

      skip = 0;
      if (( flag & OP_SOL ) && ( start_skip > 0 )) {
	     emitted_dwords++;
	     flag &= ~OP_SOL;
	     skip = start_skip;
      }

      emitted_dwords++;			/* opcode dword */
      if (operation != OP_SKIP )
	 emitted_dwords++;		/* address dword */

      *target_buffer += width * pixel_width;
      bktr->current_col += width;

  } else {

	if (bktr->current_col == 0 && width == cols) {
	    flag = OP_SOL ;
	    flag2 = OP_EOL;
        } else if (bktr->current_col == 0 ) {
	    flag = OP_SOL;
	    flag2 = 0;
	} else if (bktr->current_col >= cols)  {
	    flag =  0;
	    flag2 = OP_EOL;
	} else {
	    flag =  0;
	    flag2 = 0;
	}

	skip = 0;
	if (( flag & OP_SOL ) && ( start_skip > 0 )) {
		emitted_dwords++;
		flag &= ~OP_SOL;
		skip = start_skip;
	}

	emitted_dwords++;			/* opcode dword 1 */
	if (operation != OP_SKIP )
	      emitted_dwords++;		/* address dword */
	*target_buffer +=  (width * pixel_width / 2) ;

	if ( operation == OP_WRITE )
		operation = OP_WRITEC;
	emitted_dwords++;			/* opcode dword 2 */
	*target_buffer +=  (width * pixel_width / 2) ;
	bktr->current_col += width;
    }
 return TRUE;
}

/* ======== verbatim: rgb_prog per-field body (2852-2883) + tail ======== */
static long build_one_field(struct bktr_h *bktr, int cols, int rows,
			    int interlace, int Bpp)
{
	unsigned long target_buffer = 0x1000000; /* fake vtophys(bigbuf) */
	unsigned long target;
	int i, width;

	emitted_dwords = 0;
	/* *dma_prog++ = OP_SYNC|RESYNC|FM1; *dma_prog++ = 0;  (2852-2855) */
	emitted_dwords += 2;
	width = cols;
	for (i = 0; i < (rows/interlace); i++) {
	    target = target_buffer;
	    if ( notclipped(bktr, i, width)) {
		split(bktr, bktr->y2 - bktr->y, OP_WRITE,
		      Bpp, &target,  cols);
	    } else {
		while(getline_h(bktr, i)) {
		    if (bktr->y != bktr->y2 ) {
			split(bktr, bktr->y2 - bktr->y, OP_WRITE,
			      Bpp, &target, cols);
		    }
		    if (bktr->yclip != bktr->yclip2 ) {
			split(bktr, bktr->yclip2 - bktr->yclip,
			      OP_SKIP, Bpp, &target,  cols);
		    }
		}
	    }
	    target_buffer += interlace * (cols*Bpp);
	}
	/* field tail: SYNC+0 + JUMP+addr (+0)  (2885-2911 / 2952-2957) */
	emitted_dwords += 4;
	return emitted_dwords;
}

/* ======== verbatim: BT848SCLIP validation (1219-1271) ======== */
static int validate_clip_list(struct bktr_h *bktr)
{
	int i;

	bktr->max_clip_node = 0;
	for (i = 0; i < BT848_MAX_CLIP_NODE; i++) {
		if (bktr->clip_list[i].y_min == 0 &&
		    bktr->clip_list[i].y_max == 0)
			break;
	}
	bktr->max_clip_node = i;

	if (bktr->max_clip_node == 0 &&
	    (bktr->clip_list[0].y_min != 0 &&
	     bktr->clip_list[0].y_max != 0)) {
		return EINVAL;
	}
	for (i = 0; i < BT848_MAX_CLIP_NODE - 1 ; i++) {
		if (bktr->clip_list[i].y_min == 0 &&
		    bktr->clip_list[i].y_max == 0) {
			break;
		}
		if ( bktr->clip_list[i+1].y_min != 0 &&
		     bktr->clip_list[i+1].y_max != 0 &&
		     bktr->clip_list[i].x_min > bktr->clip_list[i+1].x_min ) {
			bktr->max_clip_node = 0;
			return (EINVAL);
		}
		if (bktr->clip_list[i].x_min >= bktr->clip_list[i].x_max ||
		    bktr->clip_list[i].y_min >= bktr->clip_list[i].y_max ||
		    bktr->clip_list[i].x_min < 0 ||
		    bktr->clip_list[i].x_max < 0 ||
		    bktr->clip_list[i].y_min < 0 ||
		    bktr->clip_list[i].y_max < 0 ) {
			bktr->max_clip_node = 0;
			return (EINVAL);
		}
	}
	return 0;
}

/* PoC clip list: 99 nodes covering every scanline, disjoint increasing y */
static void build_poc_clips(struct bktr_h *bktr)
{
	int k;
	memset(bktr->clip_list, 0, sizeof(bktr->clip_list));
	for (k = 0; k < 99; k++) {
		bktr->clip_list[k].x_min = 0;
		bktr->clip_list[k].x_max = 2046;	/* covers every row */
		bktr->clip_list[k].y_min = 2*k;		/* disjoint, increasing */
		bktr->clip_list[k].y_max = 2*k + 1;
	}
	/* clip_list[99] = all-zero terminator */
}

int main(void)
{
	struct bktr_h bktr;
	int rc;
	long field1, field2;

	printf("DMA_PROG_ALLOC = %d bytes = %d dwords per program buffer\n",
	       DMA_PROG_ALLOC, DMA_PROG_DWORDS);

	build_poc_clips(&bktr);
	rc = validate_clip_list(&bktr);
	printf("[*] PoC clip list validation (BT848SCLIP): rc=%d "
	       "(0 = accepted by driver), max_clip_node=%d\n",
	       rc, bktr.max_clip_node);
	if (rc != 0) { printf("clip list rejected -- harness bug\n"); return 1; }

	/* --- config A: default geometry 640x480, RGB16 (Bpp=2), interlaced --- */
	field1 = build_one_field(&bktr, 640, 480, 2, 2);	/* dma_prog */
	field2 = build_one_field(&bktr, 640, 480, 2, 2);	/* odd_dma_prog */
	printf("[A] 640x480 default, 99 clips: field1=%ld field2=%ld dwords "
	       "(cap %d) -> overflow %ldx / %ldx\n",
	       field1, field2, DMA_PROG_DWORDS,
	       field1 / DMA_PROG_DWORDS, field2 / DMA_PROG_DWORDS);

	/* --- config B: max legal geometry 2046x1022 --- */
	field1 = build_one_field(&bktr, 1022, 2046, 2, 2);
	field2 = build_one_field(&bktr, 1022, 2046, 2, 2);
	printf("[B] 2046x1022 max, 99 clips: field1=%ld field2=%ld dwords "
	       "(cap %d) -> overflow %ldx / %ldx\n",
	       field1, field2, DMA_PROG_DWORDS,
	       field1 / DMA_PROG_DWORDS, field2 / DMA_PROG_DWORDS);

	/* --- config C: yuv422_prog odd-only, NO clip list at all ---
	 * (bktr_core.c:3126-3133: 5 dwords per line, rows lines at interlace=1;
	 *  SETGEO rows=2046 + ONLY_ODD, SACTPIXFMT YUV 4:2:2 = pixfmt 9) */
	{
		int rows = 2046;
		long yuv = 2 + 5L * rows + 4;	/* head + loop + tail */
		printf("[C] yuv422 odd-only rows=2046, 0 clips: %ld dwords "
		       "(cap %d) -> overflow %ldx (%ld bytes past end)\n",
		       yuv, DMA_PROG_DWORDS, yuv / DMA_PROG_DWORDS,
		       yuv * 4 - DMA_PROG_ALLOC);
		if (yuv > DMA_PROG_DWORDS)
			printf("    CONFIRMED: geometry-only overflow, "
			       "no clip list required\n");
	}

	if (field1 > DMA_PROG_DWORDS && field2 > DMA_PROG_DWORDS)
		printf("CONFIRMED: RISC emission overruns the 8-page "
		       "dma_prog/odd_dma_prog buffers -> linear kernel heap "
		       "overflow (bytes past end: %ld / %ld)\n",
		       field1*4 - DMA_PROG_ALLOC, field2*4 - DMA_PROG_ALLOC);
	return !(field1 > DMA_PROG_DWORDS && field2 > DMA_PROG_DWORDS);
}