/*
 * DF-1216 harness — METEORSETGEO + METEORSACTPIXFMT pixel-format mismatch
 *
 * The guest has no bt848 PCI video-capture device (QEMU does not emulate it),
 * so /dev/bktr0 cannot be opened and the in-kernel path cannot be triggered
 * on this host. This harness instead extracts the *exact buggy arithmetic*
 * from sys/dev/video/bktr/bktr_core.c and demonstrates that the OOB read/
 * write offset is real and attacker-controlled:
 *
 *   METEORSETGEO  line 1657:  bigbuf = rows*cols*frames*2          (hardcoded *2)
 *   METEORSACTPIXFMT line 2333: bktr->pixfmt = arg;                (no realloc)
 *   video_read    line 1084:  count = rows*cols*pixfmt_table[pixfmt].Bpp
 *   video_read    line 1106:  uiomove(bigbuf, count, uio)          (OOB read)
 *   start_capture line 3521:  bzero(bigbuf, rows*cols*frames*Bpp)  (OOB write)
 *
 * It uses the genuine pixfmt_table from bktr_core.c:220-238 and the genuine
 * METEOR_GEO_YUV_422 -> pixfmt[10] (Bpp=2) -> METEORSACTPIXFMT(5) (Bpp=4)
 * sequence the in-kernel bug uses. Build with `cc -O2 -Wall -o harness
 * harness.c`; the bug is signalled by the program returning non-zero and
 * printing OOB=positive.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Subset of pixfmt_table from sys/dev/video/bktr/bktr_core.c:220 */
struct meteor_pixfmt { int type; int Bpp; };
static const struct meteor_pixfmt pixfmt_table[] = {
    { 0, 2 }, { 0, 2 }, { 0, 2 }, { 0, 2 },     /* 0..3 RGB16 */
    { 0, 3 },                                    /* 4 RGB24 */
    { 0, 4 }, { 0, 4 }, { 0, 4 }, { 0, 4 },     /* 5..8 RGB32 */
    { 1, 2 }, { 2, 2 }, { 3, 2 }                 /* 9..11 YUV* */
};
#define PIXFMT_TABLE_SIZE (sizeof(pixfmt_table)/sizeof(pixfmt_table[0]))

#define PAGE_SIZE 4096
#define btoc(x)   (((x) + PAGE_SIZE - 1) / PAGE_SIZE)

/* Minimal bktr state — only fields touched by the cited arithmetic. */
struct bktr {
    int rows, cols, frames;
    int alloc_pages;        /* in pages */
    unsigned char *bigbuf;  /* alloc_pages * PAGE_SIZE bytes */
    int pixfmt;
};

/* METEORSETGEO body from sys/dev/video/bktr/bktr_core.c:1657-1682 (verbatim
 * arithmetic; kmalloc/get_bktr_mem collapsed to malloc). */
static int METEORSETGEO(struct bktr *b, int rows, int cols, int frames)
{
    int temp = rows * cols * frames * 2;   /* line 1657 */
    temp = btoc(temp);                      /* line 1663 */
    b->alloc_pages = temp;
    b->bigbuf = calloc((size_t)temp * PAGE_SIZE, 1);
    if (!b->bigbuf) return -1;
    b->rows = rows; b->cols = cols; b->frames = frames;
    return 0;
}

/* METEORSACTPIXFMT body from sys/dev/video/bktr/bktr_core.c:2328-2337. */
static int METEORSACTPIXFMT(struct bktr *b, int idx)
{
    if (idx < 0 || idx >= (int)PIXFMT_TABLE_SIZE) return -1;
    b->pixfmt = idx;        /* line 2333 — NO size check, NO realloc */
    return 0;
}

/* video_read from sys/dev/video/bktr/bktr_core.c:1069-1114. */
static int video_read_oob_bytes(struct bktr *b)
{
    int count = b->rows * b->cols *
                pixfmt_table[b->pixfmt].Bpp;            /* line 1084-1085 */
    size_t have = (size_t)b->alloc_pages * PAGE_SIZE;
    if ((size_t)count > have)
        return (int)((size_t)count - have);             /* OOB read length */
    return 0;
}

/* start_capture bzero from sys/dev/video/bktr/bktr_core.c:3519-3524. */
static int start_capture_oob_bytes(struct bktr *b)
{
    size_t bytes = (size_t)b->rows * b->cols * b->frames *
                   pixfmt_table[b->pixfmt].Bpp;          /* line 3522-3523 */
    size_t have = (size_t)b->alloc_pages * PAGE_SIZE;
    if (bytes > have) return (int)(bytes - have);        /* OOB write length */
    return 0;
}

int main(void)
{
    struct bktr b; memset(&b, 0, sizeof(b));

    /* METEOR_GEO_YUV_422 is the canonical Bpp=2 layout; with 1 frame the
     * METEORSETGEO alloc gives a 4 MiB buffer for a 2046x1022 image. */
    if (METEORSETGEO(&b, 2046, 1022, 1) < 0) {
        perror("calloc"); return 2;
    }
    printf("After METEORSETGEO(YUV_422): bigbuf=%zu bytes alloc_pages=%d\n",
           (size_t)b.alloc_pages * PAGE_SIZE, b.alloc_pages);

    /* Switch pixfmt to index 5 (RGB 4Bpp) — no realloc. */
    if (METEORSACTPIXFMT(&b, 5) < 0) { fprintf(stderr, "pixfmt\n"); return 2; }

    int read_oob  = video_read_oob_bytes(&b);
    int write_oob = start_capture_oob_bytes(&b);
    printf("video_read OOB=%d bytes  start_capture OOB=%d bytes\n",
           read_oob, write_oob);

    free(b.bigbuf);

    if (read_oob > 0 && write_oob > 0) {
        printf("CONFIRMED: read() reads %d bytes past bigbuf end (heap leak); "
               "start_capture bzero/DMA writes %d bytes past bigbuf end "
               "(heap corruption)\n", read_oob, write_oob);
        return 0;
    }
    fprintf(stderr, "NOT CONFIRMED\n");
    return 1;
}
