/*
 * neg_usbhr.c -- pass-2 negative-evidence + latent-primitive rig for
 *                sys/bus/u4b/usb_handle_request.c (DragonFlyBSD USB
 *                device-side / gadget EP0 request engine).
 *
 * Threat model: malicious USB HOST driving crafted EP0 setups against
 * the gadget stack (sibling of ustorage_fs / usb_template).
 *
 * This KLD #includes the PRODUCTION usb_handle_request.c (symlinked
 * into this directory from /usr/src/sys/bus/u4b/usb_handle_request.c)
 * and therefore executes the real, unmodified static functions:
 *
 *   usb_handle_request()          -- full setup demux + clamp engine
 *   usb_handle_iface_request()    -- iface broadcast/unicast dispatch
 *   usb_handle_set_config()       -- (NOT runtime-driven, see VERDICT)
 *   usb_check_alt_setting()       -- SET_INTERFACE alt validation
 *   usb_handle_get_stall()        -- GET_STATUS(endpoint)
 *   usb_handle_set_stall()        -- CLEAR/SET FEATURE(endpoint halt)
 *   usb_handle_remote_wakeup()    -- (NOT runtime-driven, see VERDICT)
 *
 * A synthetic struct usb_xfer / usb_xfer_root / usb_device / usb_bus
 * graph is built with guard-patterned buffers:
 *
 *   - ep0 DMA window: 8 usable bytes (SETUP, frame 0) + 1024 usable
 *     bytes (data, frame 1), exactly the real ctrl xfer geometry
 *     (USB_EP0_BUFSIZE==1024, proxy +8, usb_transfer.c:76/666-727),
 *     surrounded by 4 KiB+ magic guard zones.
 *   - descriptor/driver provider buffers similarly guard-patterned.
 *
 * Every battery step replays the host<->device control transfer
 * fragment loop with the exact control_rem bookkeeping of
 * usbd_setup_ctrl_transfer() (usb_transfer.c:1507-1654):
 *
 *   len = frlengths[0]+frlengths[1] (= max_len, frlengths[0]==0)
 *   assert(len <= control_rem)                  [:1608]
 *   force_short_xfer ? control_rem = 0          [:1616-1617]
 *                    : control_rem -= len       [:1626]
 *   control_act = control_rem>0 || manual_status [:1631-1634]
 *
 * After every production-code call the rig checks:
 *   (C1) frlengths[1] <= 1024 (max_data_length clamp, :750)
 *   (C2) the frame-1 data window [ptr, ptr+len) lies inside the
 *        current provider window (driver buffer / template descriptor
 *        / 2-byte mcopy object), when the provider is conformant
 *   (C3) all magic guard zones intact (no OOB write anywhere)
 *   (C4) off/control_rem accounting exact: off == wLength-control_rem
 *
 * A canary self-test proves the guard checker detects 1-byte writes.
 *
 * Latent-primitive demonstrations (expected, counted separately):
 *   (P1) a template provider whose length SHRINKS below the already
 *        transferred offset makes "max_len -= off" (:746) wrap; the
 *        :750/:753 clamps bound the length to 1024/rem but the window
 *        escapes the provider object -> OOB DMA read (IN) / write
 *        (OUT) primitive.  No in-tree provider can shrink (all are
 *        pure functions of the immutable re-read SETUP bytes), so
 *        this is a hardening finding, not an in-tree exploit.
 *   (P2) same for a lying sub-driver via the real devmethod dispatch.
 *   (P3) a driver setting *plen but not *pptr yields a NULL frame
 *        pointer with nonzero length.
 *
 * Build: see build.sh / Makefile (guest, root, /usr/src present).
 * Run:   kldload ./negusbhr.ko ; output on console/dmesg ("NEG:").
 */
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/lock.h>
#include <sys/thread.h>
#include <sys/kobj.h>
#include <sys/bus.h>
#include <sys/bus_private.h>

/*------------------------------------------------------------------------
 * PRODUCTION CODE under test (symlink in this directory):
 */
#include "usb_handle_request.c"

/*------------------------------------------------------------------------*/
/* guarded buffer helper */

#define GUARD_BYTE	0xA5
#define GUARD_SZ	4096

struct gbuf {
	uint8_t	*raw;
	uint8_t	*base;			/* usable window */
	size_t	 len;			/* usable length */
};

static MALLOC_DEFINE(M_NEGUSBHR, "negusbhr", "NEG USB handle request rig");

static int
gbuf_alloc(struct gbuf *g, size_t len)
{
	g->raw = kmalloc(GUARD_SZ + len + GUARD_SZ, M_NEGUSBHR, M_WAITOK | M_ZERO);
	if (g->raw == NULL)
		return (1);
	memset(g->raw, GUARD_BYTE, GUARD_SZ);
	memset(g->raw + GUARD_SZ, 0x33, len);
	memset(g->raw + GUARD_SZ + len, GUARD_BYTE, GUARD_SZ);
	g->base = g->raw + GUARD_SZ;
	g->len = len;
	return (0);
}

static void
gbuf_free(struct gbuf *g)
{
	if (g->raw) {
		kfree(g->raw, M_NEGUSBHR);
		g->raw = NULL;
	}
}

/* returns number of violated guard bytes */
static size_t
gbuf_check(const struct gbuf *g)
{
	size_t n = 0, i;

	if (g->raw == NULL)
		return (0);
	for (i = 0; i < GUARD_SZ; i++) {
		if (g->raw[i] != GUARD_BYTE)
			n++;
		if (g->raw[GUARD_SZ + g->len + i] != GUARD_BYTE)
			n++;
	}
	return (n);
}

/*------------------------------------------------------------------------*/
/* fake object graph */

static struct lock neg_xfer_lock;

static struct usb_bus neg_bus;
static struct usb_device neg_udev;
static struct usb_xfer_root neg_root;
static struct usb_xfer neg_xfer;
static struct usb_page_cache neg_pc[2];
static usb_frlength_t neg_frlengths[2];
static struct usb_interface neg_ifaces[4];
static struct usb_endpoint neg_eps[4];
static struct usb_endpoint_descriptor neg_epdesc[4];

static struct gbuf neg_ep0buf;		/* local_buffer: 8 + 1024 usable */

/* provider window the current fragment is allowed to touch */
struct provwin {
	uint8_t	*base;
	size_t	 len;
	int	 known;
	const char *tag;
};
static struct provwin cur_prov;
/* pre-set provider window installed by each run_ctrl() (builtin paths) */
static struct provwin pre_prov;
static int pre_prov_set;

/*---------------- fake bus methods (set/clear stall) ----------------*/

static void
neg_xfer_stall(struct usb_xfer *xfer)
{
	/* nothing */
}

static void
neg_set_stall(struct usb_device *udev, struct usb_endpoint *ep,
    uint8_t *did_stall)
{
	/* nothing */
}

static void
neg_clear_stall(struct usb_device *udev, struct usb_endpoint *ep)
{
	/* nothing */
}

static struct usb_bus_methods neg_bus_methods = {
	.xfer_stall = &neg_xfer_stall,
	.set_stall = &neg_set_stall,
	.clear_stall = &neg_clear_stall,
};

/*---------------- hostile template provider -------------------------*/

static usb_handle_req_t neg_provider;

struct prov_script {
	uint16_t	len[8];		/* per-frag full lengths */
	uint8_t		stall;		/* return USB_ERR_STALLED */
};
static struct prov_script prov_cfg;
static uint32_t prov_frag;
static struct gbuf prov_buf;		/* provider object storage */

static usb_error_t
neg_provider(struct usb_device *udev, struct usb_device_request *req,
    const void **pPtr, uint16_t *pLength)
{
	uint16_t len;

	if (prov_cfg.stall) {
		*pPtr = NULL;
		*pLength = 0;
		return (USB_ERR_STALLED);
	}
	len = prov_cfg.len[prov_frag < 8 ? prov_frag : 7];
	prov_frag++;

	*pPtr = prov_buf.base;
	*pLength = len;

	/* model: provider object is exactly len bytes at buf base */
	cur_prov.base = prov_buf.base;
	cur_prov.len = len;
	cur_prov.known = 1;
	cur_prov.tag = "template";
	return (0);
}

/*---------------- fake sub-dev (bare kobj, no newbus attach) --------*/

/*
 * A struct bsd_device whose first field is KOBJ_FIELDS, brought up via
 * kobj_init() against the real driver_t method table.  This exercises
 * the production USB_HANDLE_REQUEST() kobj dispatch and
 * device_is_attached() exactly as usb_handle_iface_request() does,
 * without touching any real bus.
 */
#define	NEGBUF_LEN	64
struct neghr_softc {
	struct gbuf	buf;
};
static struct bsd_device neg_fdev;	/* zeroed + kobj_init'd */
static struct neghr_softc neg_sc;
static int g_have_drv;

static int neghr_drv_mode;		/* 0 decline, 1 conformant, 2 lie, 3 no-ptr */
static uint16_t neghr_plen0, neghr_plenN;
static size_t neghr_buflen;		/* real buffer size */

/* (re)allocate the driver buffer at neghr_buflen */
static int
neghr_realloc(void)
{
	gbuf_free(&neg_sc.buf);
	memset(&neg_sc, 0, sizeof(neg_sc));
	return (gbuf_alloc(&neg_sc.buf,
	    neghr_buflen ? neghr_buflen : NEGBUF_LEN));
}

static int
neghr_handle_request(device_t dev,
    const void *preq, void **pptr, uint16_t *plen,
    uint16_t offset, uint8_t *pstate)
{
	struct neghr_softc *sc = device_get_softc(dev);

	switch (neghr_drv_mode) {
	case 0:
		return (ENXIO);		/* decline -> builtin handler */
	case 1:				/* conformant g_audio-style */
		if (offset == 0) {
			*plen = neghr_plen0;
			*pptr = sc->buf.base;
		} else {
			*plen = 0;
		}
		cur_prov.base = sc->buf.base;
		cur_prov.len = sc->buf.len;
		cur_prov.known = 1;
		cur_prov.tag = "driver";
		return (0);
	case 2:				/* lying length (contract violation) */
		if (offset == 0) {
			*plen = neghr_plen0;
			*pptr = sc->buf.base;
		} else {
			*plen = neghr_plenN;
			*pptr = sc->buf.base + offset;
		}
		cur_prov.base = sc->buf.base;
		cur_prov.len = sc->buf.len;
		cur_prov.known = 1;
		cur_prov.tag = "driver";
		return (0);
	case 3:				/* sets *plen but not *pptr */
		if (offset == 0) {
			*plen = neghr_plen0;
		}
		return (0);
	}
	return (ENXIO);
}

static device_method_t neghr_methods[] = {
	DEVMETHOD(usb_handle_request, neghr_handle_request),
	DEVMETHOD_END
};

static driver_t neghr_driver = {
	"neghr",
	neghr_methods,
	sizeof(struct neghr_softc)
};

/*------------------------------------------------------------------------*/
/* config descriptor blob (valid, parsed by production usbd_get_no_alts) */

struct neg_dscr {
	struct usb_config_descriptor		cd;
	struct usb_interface_descriptor		if0_alt0;
	struct usb_interface_descriptor		if0_alt1;
	struct usb_interface_descriptor		if0_alt2;
	struct usb_interface_descriptor		if1_alt0;
	struct usb_endpoint_descriptor		ep0d;
	struct usb_endpoint_descriptor		ep1d;
} __packed;

static struct neg_dscr neg_dscr = {
	.cd = {
		.bLength = sizeof(struct usb_config_descriptor),
		.bDescriptorType = UDESC_CONFIG,
		.wTotalLength[0] = sizeof(struct neg_dscr) & 0xFF,
		.wTotalLength[1] = sizeof(struct neg_dscr) >> 8,
		.bNumInterface = 2,
		.bConfigurationValue = 1,
		.iConfiguration = 0,
		.bmAttributes = UC_SELF_POWERED,
		.bMaxPower = 50,
	},
	.if0_alt0 = {
		.bLength = sizeof(struct usb_interface_descriptor),
		.bDescriptorType = UDESC_INTERFACE,
		.bInterfaceNumber = 0,
		.bAlternateSetting = 0,
		.bNumEndpoints = 2,
		.bInterfaceClass = 0xFF,
		.bInterfaceSubClass = 0,
		.bInterfaceProtocol = 0,
		.iInterface = 0,
	},
	.if0_alt1 = {
		.bLength = sizeof(struct usb_interface_descriptor),
		.bDescriptorType = UDESC_INTERFACE,
		.bInterfaceNumber = 0,
		.bAlternateSetting = 1,
	},
	.if0_alt2 = {
		.bLength = sizeof(struct usb_interface_descriptor),
		.bDescriptorType = UDESC_INTERFACE,
		.bInterfaceNumber = 0,
		.bAlternateSetting = 2,
	},
	.if1_alt0 = {
		.bLength = sizeof(struct usb_interface_descriptor),
		.bDescriptorType = UDESC_INTERFACE,
		.bInterfaceNumber = 1,
		.bAlternateSetting = 0,
	},
	.ep0d = {
		.bLength = sizeof(struct usb_endpoint_descriptor),
		.bDescriptorType = UDESC_ENDPOINT,
		.bEndpointAddress = UE_DIR_IN | 1,
		.bmAttributes = UE_BULK,
		.wMaxPacketSize = {64, 0},
	},
	.ep1d = {
		.bLength = sizeof(struct usb_endpoint_descriptor),
		.bDescriptorType = UDESC_ENDPOINT,
		.bEndpointAddress = UE_DIR_OUT | 1,
		.bmAttributes = UE_BULK,
		.wMaxPacketSize = {64, 0},
	},
};

/*------------------------------------------------------------------------*/
/* results */

static int g_pass, g_fail, g_esc;	/* contained, failed, latent-escape */
static int g_esc_expected;		/* current case expects an escape */

static void
neg_result(const char *name, int contained, int escape, const char *note)
{
	if (escape) {
		kprintf("NEG: [%s] LATENT-PRIMITIVE observed: %s\n",
		    name, note ? note : "");
		g_esc++;
	} else if (contained) {
		kprintf("NEG: [%s] contained\n", name);
		g_pass++;
	} else {
		kprintf("NEG: [%s] FAILED CONTAINMENT: %s\n",
		    name, note ? note : "");
		g_fail++;
	}
}

/*------------------------------------------------------------------------*/
/* the fragment stepper */

struct stepres {
	uint32_t	frags;
	uint32_t	sent_total;	/* bytes handed to the data stage */
	int		end_stalled;	/* STALLED return (incl. completion) */
	int		end_badctx;
	uint16_t	last_max_len;
	uintptr_t	last_ptr;
	int		escaped;	/* window escape observed (any frag) */
	size_t		esc_bytes;
	int		null_frame;	/* NULL frame ptr with len > 0 */
	int		guard_hit;
};

/*------------------------------------------------------------------------*/
/* the fragment stepper */

/*
 * Drive one full control transfer against production usb_handle_request().
 * Replicates the control_rem lifecycle of usb_transfer.c: the dummy
 * 0xFFFF value (:1584) is replaced by wLength on the first data-stage
 * submit (usbd_control_transfer_init, :1548-1550), then per fragment:
 * len <= control_rem asserted (:1608), force_short -> control_rem = 0
 * (:1616-1617), else control_rem -= len (:1626).
 */
static void
run_ctrl(struct stepres *rs, struct usb_device_request *req, int max_frags)
{
	uint16_t wLength = UGETW(req->wLength);
	uint32_t frag;
	int inited = 0;

	memset(rs, 0, sizeof(*rs));

	/* fresh xfer state, mirroring usbd_ctrl_transfer_setup geometry */
	memset(&neg_xfer.flags, 0, sizeof(neg_xfer.flags));
	memset(&neg_xfer.flags_int, 0, sizeof(neg_xfer.flags_int));
	neg_xfer.usb_state = USB_ST_SETUP;
	neg_xfer.flags_int.control_act = 1;
	neg_xfer.flags_int.control_rem = 0xFFFF;	/* dummy, :1584 */
	neg_xfer.nframes = 1;
	neg_xfer.error = 0;
	neg_xfer.aframes = 0;
	neg_frlengths[0] = sizeof(*req);
	neg_frlengths[1] = 0;
	neg_pc[0].buffer = neg_ep0buf.base;		/* :507 resets anyway */
	neg_pc[1].buffer = neg_ep0buf.base + sizeof(*req);
	neg_pc[0].page_start = NULL;
	neg_pc[1].page_start = NULL;

	/* host delivered the SETUP packet into frame 0 */
	memcpy(neg_ep0buf.base, req, sizeof(*req));

	cur_prov.known = 0;
	cur_prov.base = NULL;
	cur_prov.len = 0;
	if (pre_prov_set)
		cur_prov = pre_prov;
	prov_frag = 0;

	for (frag = 0; frag < (uint32_t)max_frags; frag++) {
		usb_error_t err;
		uint16_t off, rem, dlen;
		uintptr_t dptr;
		int escaped = 0;

		rs->frags = frag + 1;

		/*
		 * Real callback context enters usb_handle_request() with
		 * the xfer lock held; the unlock/relock-taking paths
		 * (usb_handle_set_stall, usb_handle_iface_request,
		 * usb_handle_set_config) give it back held.  Model that.
		 */
		lockmgr(&neg_xfer_lock, LK_EXCLUSIVE);
		err = usb_handle_request(&neg_xfer);
		if (lockowned(&neg_xfer_lock))
			lockmgr(&neg_xfer_lock, LK_RELEASE);
		if (err == USB_ERR_STALLED) {
			/* includes "complete" re-arm (:741,:796-799) */
			rs->end_stalled = 1;
			return;
		}
		if (err == USB_ERR_BAD_CONTEXT) {
			rs->end_badctx = 1;
			return;
		}
		if (err != 0) {
			rs->end_stalled = 2;	/* unexpected error code */
			return;
		}

		dlen = neg_frlengths[1];
		dptr = (uintptr_t)neg_pc[1].buffer;
		rs->last_max_len = dlen;
		rs->last_ptr = dptr;

		/* ---- invariant checks ---- */

		/* C1: max_data_length clamp */
		if (dlen > 1024) {
			kprintf("NEG:   frag%u C1 VIOLATED frlengths[1]=%u "
			    "> 1024\n", frag, dlen);
			rs->guard_hit |= 0x100;
		}

		/* C2: window containment */
		if (dlen > 0 && dptr == (uintptr_t)NULL) {
			rs->null_frame = 1;
		} else if (cur_prov.known && dlen > 0) {
			uintptr_t lo = dptr, hi = dptr + dlen;
			uintptr_t plo = (uintptr_t)cur_prov.base;
			uintptr_t phi = plo + cur_prov.len;
			if (lo < plo || hi > phi) {
				size_t esc = (hi > phi) ? (hi - phi) :
				    (plo > lo ? plo - lo : 0);
				escaped = 1;
				rs->escaped = 1;
				rs->esc_bytes += esc;
				kprintf("NEG:   frag%u window [%p,%p) vs "
				    "provider(%s) [%p,%p): ESCAPE %zu bytes\n",
				    frag, (void *)lo, (void *)hi, cur_prov.tag,
				    (void *)plo, (void *)phi, esc);
			}
		} else if (dlen > 0) {
			/* mcopy path: frame stays inside the ep0 DMA window */
			uintptr_t lo = dptr, hi = dptr + dlen;
			uintptr_t plo = (uintptr_t)neg_ep0buf.base;
			uintptr_t phi = plo + 8 + 1024;
			if (lo < plo || hi > phi) {
				kprintf("NEG:   frag%u mcopy window [%p,%p) "
				    "outside ep0 DMA window\n", frag,
				    (void *)lo, (void *)hi);
				rs->guard_hit |= 0x400;
			}
		}

		/* C3: guard zones */
		if (gbuf_check(&neg_ep0buf) || gbuf_check(&prov_buf) ||
		    (g_have_drv && gbuf_check(&neg_sc.buf))) {
			kprintf("NEG:   frag%u GUARD VIOLATION\n", frag);
			rs->guard_hit = 1;
		}

		if (escaped && !g_esc_expected) {
			/* unexpected escape = containment failure */
			neg_result("step", 0, 0, "unexpected window escape");
		}

		rs->sent_total += dlen;

		/* ---- replicate usbd_setup_ctrl_transfer bookkeeping ---- */
		if (!inited) {
			/* first data-stage submit: usbd_control_transfer_init */
			neg_xfer.flags_int.control_rem = wLength;
			inited = 1;
		}
		off = wLength;
		rem = neg_xfer.flags_int.control_rem;
		off -= rem;				/* off = wLength - rem */
		(void)off;

		/* :1608 invariant */
		if (dlen > rem) {
			kprintf("NEG:   frag%u BOOKKEEPING VIOLATION "
			    "len=%u > control_rem=%u\n", frag, dlen, rem);
			rs->guard_hit |= 0x200;
			return;
		}
		if (neg_xfer.flags.force_short_xfer)
			neg_xfer.flags_int.control_rem = 0;	/* :1616 */
		else
			neg_xfer.flags_int.control_rem -= dlen;	/* :1626 */

		if (neg_xfer.flags_int.control_rem == 0 &&
		    !neg_xfer.flags.manual_status) {
			/* status stage auto-executed; host moves on */
			neg_xfer.flags_int.control_act = 0;
			neg_xfer.usb_state = USB_ST_TRANSFERRED;
			/* one more callback completes the transfer */
			continue;
		}
		if (neg_xfer.flags_int.control_act == 0)
			return;			/* transfer complete */

		neg_xfer.usb_state = USB_ST_TRANSFERRED;
		neg_xfer.aframes = 1;
	}
}

/*------------------------------------------------------------------------*/
/* battery */

#define	REQ(bm, br, val, idx, len) (struct usb_device_request){	\
	.bmRequestType = (bm), .bRequest = (br),			\
	.wValue = { (val) & 0xFF, (val) >> 8 },			\
	.wIndex = { (idx) & 0xFF, (idx) >> 8 },			\
	.wLength = { (len) & 0xFF, (len) >> 8 } }

static void
set_prov(uint16_t l0, uint16_t l1, uint16_t l2, uint16_t l3, uint8_t stall)
{
	memset(&prov_cfg, 0, sizeof(prov_cfg));
	prov_cfg.len[0] = l0;
	prov_cfg.len[1] = l1;
	prov_cfg.len[2] = l2;
	prov_cfg.len[3] = l3;
	prov_cfg.stall = stall;
}

static void
set_drv(int mode, uint16_t plen0, uint16_t plenN, size_t buflen)
{
	neghr_drv_mode = mode;
	neghr_plen0 = plen0;
	neghr_plenN = plenN;
	neghr_buflen = buflen;
}

static void
battery(void)
{
	struct stepres rs;

	kprintf("NEG: battery start\n");

	/*---------------- internal (mcopy) paths ----------------*/

	neg_udev.curr_config_no = 3;
	run_ctrl(&rs, &REQ(0x80, UR_GET_CONFIG, 0, 0, 1), 4);
	neg_result("I01 GET_CONFIG wL=1",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 1 &&
	    rs.end_stalled == 1, 0, NULL);

	neg_udev.curr_config_no = 3;
	run_ctrl(&rs, &REQ(0x80, UR_GET_CONFIG, 0, 0, 64), 4);
	neg_result("I02 GET_CONFIG wL=64 (short-terminate)",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 1, 0,
	    NULL);

	neg_udev.curr_config_no = 3;
	run_ctrl(&rs, &REQ(0x80, UR_GET_CONFIG, 0, 0, 0), 4);
	neg_result("I03 GET_CONFIG wL=0",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 0, 0,
	    NULL);

	neg_udev.flags.remote_wakeup = 1;
	neg_udev.flags.self_powered = 1;
	run_ctrl(&rs, &REQ(0x80, UR_GET_STATUS, 0, 0, 2), 4);
	neg_result("I04 GET_STATUS(dev) wL=2",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 2, 0,
	    NULL);

	neg_udev.flags.remote_wakeup = 0;
	run_ctrl(&rs, &REQ(0x80, UR_GET_STATUS, 0, 0, 8), 4);
	neg_result("I05 GET_STATUS(dev) wL=8",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 2, 0,
	    NULL);

	run_ctrl(&rs, &REQ(0x80, UR_GET_STATUS, 0, 0, 0xFFFF), 4);
	neg_result("I06 GET_STATUS(dev) wL=0xFFFF (wrap feeder)",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 2, 0,
	    NULL);

	run_ctrl(&rs, &REQ(0x80, UR_GET_STATUS, 0, 0, 0), 4);
	neg_result("I07 GET_STATUS(dev) wL=0",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 0, 0,
	    NULL);

	run_ctrl(&rs, &REQ(0x80, UR_GET_STATUS, 0, 0x81, 2), 4);
	neg_result("I08 GET_STATUS(ep 0x81) wL=2",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 2, 0,
	    NULL);

	run_ctrl(&rs, &REQ(0x80, UR_GET_STATUS, 0, 0x7F, 2), 4);
	neg_result("I09 GET_STATUS(ep 0x7F nonexistent)",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 2, 0,
	    NULL);

	neg_udev.curr_config_no = 0;
	run_ctrl(&rs, &REQ(0x00, UR_SET_ADDRESS, 0x7F, 0, 0), 4);
	neg_result("I10 SET_ADDRESS 0x7F (valid)",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 0 &&
	    neg_udev.address == 0x7F && rs.end_badctx == 1, 0, NULL);

	neg_udev.curr_config_no = 0;
	run_ctrl(&rs, &REQ(0x00, UR_SET_ADDRESS, 0x80, 0, 0), 4);
	neg_result("I11 SET_ADDRESS 0x80 (invalid)",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.end_stalled == 1, 0,
	    NULL);

	neg_udev.curr_config_no = 0;
	run_ctrl(&rs, &REQ(0x00, UR_SET_ADDRESS, 0xFFFF, 0, 0), 4);
	neg_result("I12 SET_ADDRESS 0xFFFF (invalid)",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.end_stalled == 1, 0,
	    NULL);

	neg_udev.curr_config_no = 1;
	run_ctrl(&rs, &REQ(0x02, UR_CLEAR_FEATURE, UF_ENDPOINT_HALT, 0x81, 0), 4);
	neg_result("I13 CLEAR_FEATURE(halt ep 0x81)",
	    rs.escaped == 0 && rs.guard_hit == 0 && neg_eps[0].is_stalled == 0,
	    0, NULL);

	run_ctrl(&rs, &REQ(0x02, UR_SET_FEATURE, UF_ENDPOINT_HALT, 0x81, 0), 4);
	neg_result("I14 SET_FEATURE(halt ep 0x81)",
	    rs.escaped == 0 && rs.guard_hit == 0 && neg_eps[0].is_stalled == 1,
	    0, NULL);

	run_ctrl(&rs, &REQ(0x02, UR_CLEAR_FEATURE, UF_ENDPOINT_HALT, 0x33, 0), 4);
	neg_result("I15 CLEAR_FEATURE(halt ep 0x33 nonexistent)",
	    rs.escaped == 0 && rs.guard_hit == 0, 0, NULL);

	/*---------------- template provider paths ----------------*/

	set_prov(18, 18, 18, 18, 0);
	run_ctrl(&rs, &REQ(0x80, UR_GET_DESCRIPTOR, (UDESC_DEVICE << 8), 0, 18), 4);
	neg_result("T01 GET_DESC(dev) len=18 wL=18 exact",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 18, 0,
	    NULL);

	set_prov(18, 18, 18, 18, 0);
	run_ctrl(&rs, &REQ(0x80, UR_GET_DESCRIPTOR, (UDESC_DEVICE << 8), 0, 64), 4);
	neg_result("T02 GET_DESC(dev) len=18 wL=64",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 18, 0,
	    NULL);

	set_prov(18, 18, 18, 18, 0);
	run_ctrl(&rs, &REQ(0x80, UR_GET_DESCRIPTOR, (UDESC_DEVICE << 8), 0, 0xFFFF), 4);
	neg_result("T03 GET_DESC(dev) len=18 wL=0xFFFF",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 18, 0,
	    NULL);

	set_prov(4096, 4096, 4096, 4096, 0);
	run_ctrl(&rs, &REQ(0x80, UR_GET_DESCRIPTOR, (UDESC_CONFIG << 8), 0, 4096), 8);
	neg_result("T04 GET_DESC(cfg) len=4096 wL=4096 multi-frag",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 4096,
	    0, NULL);

	set_prov(2048, 2048, 2048, 2048, 0);
	run_ctrl(&rs, &REQ(0x80, UR_GET_DESCRIPTOR, (UDESC_CONFIG << 8), 0, 4096), 8);
	neg_result("T05 GET_DESC(cfg) len=2048 wL=4096 (short)",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 2048, 0,
	    NULL);

	set_prov(1024, 1024, 1024, 1024, 0);
	run_ctrl(&rs, &REQ(0x80, UR_GET_DESCRIPTOR, (UDESC_CONFIG << 8), 0, 1024), 8);
	neg_result("T06 GET_DESC(cfg) len=1024 wL=1024 exact",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 1024,
	    0, NULL);

	set_prov(1025, 1025, 1025, 1025, 0);
	run_ctrl(&rs, &REQ(0x80, UR_GET_DESCRIPTOR, (UDESC_CONFIG << 8), 0, 4096), 8);
	neg_result("T07 GET_DESC(cfg) len=1025 wL=4096",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 1025, 0,
	    NULL);

	set_prov(255, 255, 255, 255, 0);
	run_ctrl(&rs, &REQ(0x80, UR_GET_DESCRIPTOR, (UDESC_STRING << 8) | 1,
	    0x0409, 255), 8);
	neg_result("T08 GET_DESC(str) len=255 wL=255 exact",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 255, 0,
	    NULL);

	set_prov(2, 2, 2, 2, 0);
	run_ctrl(&rs, &REQ(0x80, UR_GET_DESCRIPTOR, (UDESC_STRING << 8) | 1,
	    0x0409, 1), 8);
	neg_result("T09 GET_DESC(str) len=2 wL=1",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 1, 0,
	    NULL);

	set_prov(1, 1, 1, 1, 0);
	run_ctrl(&rs, &REQ(0x80, UR_GET_DESCRIPTOR, (UDESC_DEVICE << 8), 0, 0xFFFF), 4);
	neg_result("T10 GET_DESC len=1 wL=0xFFFF",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 1, 0,
	    NULL);

	set_prov(0, 0, 0, 0, 1);
	run_ctrl(&rs, &REQ(0x40, 0x99, 0, 0, 64), 4);	/* vendor garbage */
	neg_result("T11 vendor req, provider stalls",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.end_stalled == 1, 0,
	    NULL);

	set_prov(52, 52, 52, 52, 0);
	run_ctrl(&rs, &REQ(0x81, 0x06, (0x22 << 8), 0, 255), 8);	/* HID-style */
	neg_result("T12 vendor-class desc len=52 wL=255 (iface decline first)",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 52, 0,
	    NULL);

	/*----------- LATENT PRIMITIVE P1: shrinking provider -------------*/

	g_esc_expected = 1;
	set_prov(4096, 100, 100, 100, 0);
	run_ctrl(&rs, &REQ(0x80, UR_GET_DESCRIPTOR, (UDESC_CONFIG << 8), 0, 4096), 8);
	kprintf("NEG:   P1a frags=%u sent=%u escaped=%u esc_bytes=%zu\n",
	    rs.frags, rs.sent_total, rs.escaped, rs.esc_bytes);
	neg_result("P1a SHRINK 4096->100 (latent wrap at :746)",
	    rs.guard_hit == 0, rs.escaped && rs.esc_bytes > 900,
	    "max_len wrapped past provider window (OOB DMA read, IN)");
	g_esc_expected = 0;

	g_esc_expected = 1;
	set_prov(1024, 4, 4, 4, 0);
	run_ctrl(&rs, &REQ(0x80, UR_GET_DESCRIPTOR, (UDESC_CONFIG << 8), 0, 4096), 8);
	kprintf("NEG:   P1b frags=%u sent=%u escaped=%u esc_bytes=%zu\n",
	    rs.frags, rs.sent_total, rs.escaped, rs.esc_bytes);
	neg_result("P1b SHRINK 1024->4 (latent wrap at :746)",
	    rs.guard_hit == 0, rs.escaped && rs.esc_bytes > 900,
	    "max_len wrapped past provider window (OOB DMA read, IN)");
	g_esc_expected = 0;

	g_esc_expected = 1;
	set_prov(200, 4, 4, 4, 0);
	run_ctrl(&rs, &REQ(0x80, UR_GET_DESCRIPTOR, (UDESC_CONFIG << 8), 0, 0xFFFF), 8);
	kprintf("NEG:   P1c frags=%u sent=%u escaped=%u esc_bytes=%zu\n",
	    rs.frags, rs.sent_total, rs.escaped, rs.esc_bytes);
	/* force_short termination after frag1: NO frag2 -> contained */
	neg_result("P1c SHRINK 200->4 wL=0xFFFF (force_short guards)",
	    rs.guard_hit == 0 && rs.escaped == 0 && rs.sent_total == 200,
	    0, NULL);
	g_esc_expected = 0;

	/*---------------- iface builtin paths ----------------*/

	neg_ifaces[0].alt_index = 0;
	run_ctrl(&rs, &REQ(0x01, UR_SET_INTERFACE, 2, 0, 0), 4);
	neg_result("F01 SET_INTERFACE alt=2 (valid, no_alts=3)",
	    rs.escaped == 0 && rs.guard_hit == 0 &&
	    neg_ifaces[0].alt_index == 2, 0, NULL);

	run_ctrl(&rs, &REQ(0x01, UR_SET_INTERFACE, 3, 0, 0), 4);
	neg_result("F02 SET_INTERFACE alt=3 (invalid)",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.end_stalled == 1, 0,
	    NULL);

	/* GET_INTERFACE is served ZERO-COPY from &iface->alt_index (:339) */
	pre_prov_set = 1;
	pre_prov.base = (uint8_t *)&neg_ifaces[0].alt_index;
	pre_prov.len = 1;
	pre_prov.known = 1;
	pre_prov.tag = "iface->alt_index";
	run_ctrl(&rs, &REQ(0x81, UR_GET_INTERFACE, 0, 0, 1), 4);
	pre_prov_set = 0;
	neg_result("F03 GET_INTERFACE (after alt=2, zero-copy 1B)",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.sent_total == 1, 0,
	    NULL);

	run_ctrl(&rs, &REQ(0x81, UR_GET_INTERFACE, 0, 3, 1), 4);
	neg_result("F04 GET_INTERFACE wIndex=3 (no idesc)",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.end_stalled == 1, 0,
	    NULL);

	run_ctrl(&rs, &REQ(0x81, UR_GET_INTERFACE, 0, 255, 1), 4);
	neg_result("F05 GET_INTERFACE wIndex=255 (OOB index)",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.end_stalled == 1, 0,
	    NULL);

	run_ctrl(&rs, &REQ(0x23, 0x99, 0, 0, 8), 4);	/* broadcast, all decline */
	neg_result("F06 broadcast req, all ifaces decline",
	    rs.escaped == 0 && rs.guard_hit == 0 && rs.end_stalled == 1, 0,
	    NULL);

	/*---------------- devmethod (real kobj dispatch) ----------------*/

	if (!g_have_drv)
		kprintf("NEG: (driver leg skipped: kobj device not "
		    "available)\n");

	if (g_have_drv) {
		set_drv(1, 8, 0, 8);
		if (neghr_realloc() == 0) {
			run_ctrl(&rs, &REQ(0xA1, 0x82, 0, 0, 8), 4);
			neg_result("D01 driver conformant read wL=8",
			    rs.escaped == 0 && rs.guard_hit == 0 &&
			    rs.sent_total == 8, 0, NULL);

			run_ctrl(&rs, &REQ(0xA1, 0x82, 0, 0, 0xFFFF), 8);
			neg_result("D02 driver conformant read wL=0xFFFF "
			    "(plen=0 at off>0)",
			    rs.escaped == 0 && rs.guard_hit == 0 &&
			    rs.sent_total == 8, 0, NULL);

			run_ctrl(&rs, &REQ(0x21, 0x01, 0, 0, 7), 4);
			neg_result("D03 driver conformant WRITE wL=7",
			    rs.escaped == 0 && rs.guard_hit == 0, 0, NULL);

			run_ctrl(&rs, &REQ(0x21, 0x01, 0, 0, 0x100), 8);
			neg_result("D04 driver conformant WRITE wL=0x100",
			    rs.escaped == 0 && rs.guard_hit == 0, 0, NULL);

			/* P2: lying driver length at off>0 */
			g_esc_expected = 1;
			set_drv(2, 1024, 300, 1024);
			neghr_realloc();
			run_ctrl(&rs, &REQ(0xA1, 0x82, 0, 0, 4096), 8);
			kprintf("NEG:   P2 frags=%u sent=%u escaped=%u "
			    "esc_bytes=%zu\n", rs.frags, rs.sent_total,
			    rs.escaped, rs.esc_bytes);
			neg_result("P2 LYING DRIVER plen 1024->300 (latent)",
			    rs.guard_hit == 0, rs.escaped,
			    "driver-trust: OOB window via plen>buffer");
			g_esc_expected = 0;

			/* P3: driver sets plen but not pptr */
			set_drv(3, 4, 0, 8);
			neghr_realloc();
			run_ctrl(&rs, &REQ(0xA1, 0x82, 0, 0, 4), 4);
			kprintf("NEG:   P3 frags=%u sent=%u null_frame=%d\n",
			    rs.frags, rs.sent_total, rs.null_frame);
			neg_result("P3 DRIVER pptr-unset plen=4 (latent)",
			    rs.guard_hit == 0, rs.null_frame,
			    "NULL frame pointer with len>0");
		}
	}

	/*---------------- error / completion states ----------------*/

	{
		struct usb_device_request r =
		    REQ(0x80, UR_GET_STATUS, 0, 0, 2);

		memcpy(neg_ep0buf.base, &r, sizeof(r));
		memset(&neg_xfer.flags, 0, sizeof(neg_xfer.flags));
		memset(&neg_xfer.flags_int, 0, sizeof(neg_xfer.flags_int));
		neg_pc[0].buffer = neg_ep0buf.base;
		neg_xfer.usb_state = USB_ST_ERROR;
		neg_xfer.flags_int.control_act = 1;
		neg_xfer.flags_int.control_rem = 0x10;
		neg_udev.curr_config_no = 1;
		{
			usb_error_t err;

			lockmgr(&neg_xfer_lock, LK_EXCLUSIVE);
			err = usb_handle_request(&neg_xfer);
			if (lockowned(&neg_xfer_lock))
				lockmgr(&neg_xfer_lock, LK_RELEASE);
			neg_result("E01 error-state GET_STATUS -> STALLED",
			    err == USB_ERR_STALLED && gbuf_check(&neg_ep0buf) == 0,
			    0, NULL);
		}
	}

	{
		struct usb_device_request r =
		    REQ(0x80, UR_GET_STATUS, 0, 0, 2);

		memcpy(neg_ep0buf.base, &r, sizeof(r));
		memset(&neg_xfer.flags_int, 0, sizeof(neg_xfer.flags_int));
		neg_xfer.usb_state = USB_ST_TRANSFERRED;
		neg_xfer.flags_int.control_act = 0;		/* completed */
		{
			usb_error_t err;

			lockmgr(&neg_xfer_lock, LK_EXCLUSIVE);
			err = usb_handle_request(&neg_xfer);
			if (lockowned(&neg_xfer_lock))
				lockmgr(&neg_xfer_lock, LK_RELEASE);
			neg_result("E02 completed-state GET_STATUS -> STALLED",
			    err == USB_ERR_STALLED, 0, NULL);
		}
	}

	{
		struct usb_device_request r =
		    REQ(0x80, UR_GET_STATUS, 0, 0, 2);

		memcpy(neg_ep0buf.base, &r, sizeof(r));
		memset(&neg_xfer.flags_int, 0, sizeof(neg_xfer.flags_int));
		neg_xfer.usb_state = USB_ST_SETUP;
		neg_xfer.flags_int.control_act = 0;		/* nothing active */
		{
			usb_error_t err;

			lockmgr(&neg_xfer_lock, LK_EXCLUSIVE);
			err = usb_handle_request(&neg_xfer);
			if (lockowned(&neg_xfer_lock))
				lockmgr(&neg_xfer_lock, LK_RELEASE);
			neg_result("E03 no-active-transfer SETUP -> STALLED",
			    err == USB_ERR_STALLED, 0, NULL);
		}
	}

	kprintf("NEG: battery done\n");
}

/*------------------------------------------------------------------------*/
/* canary: prove the guard checker detects corruption */

static void
canary_test(void)
{
	size_t n;

	neg_ep0buf.raw[GUARD_SZ - 1] ^= 0xFF;
	n = gbuf_check(&neg_ep0buf);
	neg_ep0buf.raw[GUARD_SZ - 1] ^= 0xFF;
	if (n != 0) {
		kprintf("NEG: canary: guard checker detects 1-byte writes "
		    "(n=%zu) OK\n", n);
	} else {
		kprintf("NEG: canary: GUARD CHECKER BROKEN\n");
		g_fail++;
	}
}

/*------------------------------------------------------------------------*/

static void
neg_setup_graph(void)
{
	memset(&neg_bus, 0, sizeof(neg_bus));
	neg_bus.methods = &neg_bus_methods;
	lockinit(&neg_bus.bus_lock, "nbuslk", 0, LK_CANRECURSE);

	memset(&neg_udev, 0, sizeof(neg_udev));
	neg_udev.bus = &neg_bus;
	neg_udev.flags.usb_mode = USB_MODE_DEVICE;
	neg_udev.ifaces = neg_ifaces;
	neg_udev.ifaces_max = 4;
	neg_udev.cdesc = &neg_dscr.cd;
	neg_udev.endpoints = neg_eps;
	neg_udev.endpoints_max = 4;
	neg_udev.curr_config_no = 1;
	neg_udev.address = 5;
	lockinit(&neg_udev.enum_lock, "nenulk", 0, LK_CANRECURSE);
	lockinit(&neg_udev.sr_lock, "nsrlk", 0, LK_CANRECURSE);

	/* control endpoint */
	memset(&neg_udev.ctrl_ep, 0, sizeof(neg_udev.ctrl_ep));
	/* endpoint descriptors */
	memset(neg_epdesc, 0, sizeof(neg_epdesc));
	neg_epdesc[0] = neg_dscr.ep0d;		/* IN 1 bulk  */
	neg_epdesc[1] = neg_dscr.ep1d;		/* OUT 1 bulk */
	neg_epdesc[2] = neg_dscr.ep0d;
	neg_epdesc[2].bEndpointAddress = UE_DIR_IN | 2;
	neg_epdesc[2].bmAttributes = UE_INTERRUPT;

	memset(neg_eps, 0, sizeof(neg_eps));
	neg_eps[0].edesc = &neg_epdesc[0];
	neg_eps[0].iface_index = 0;
	neg_eps[0].is_stalled = 1;
	neg_eps[1].edesc = &neg_epdesc[1];
	neg_eps[1].iface_index = 0;
	/* eps[2]: edesc NULL */
	neg_eps[3].edesc = &neg_epdesc[2];
	neg_eps[3].iface_index = 1;

	memset(neg_ifaces, 0, sizeof(neg_ifaces));
	neg_ifaces[0].idesc = &neg_dscr.if0_alt0;
	neg_ifaces[1].idesc = &neg_dscr.if1_alt0;
	/* ifaces[2]: idesc NULL (broadcast terminator) */
	/* ifaces[3]: idesc NULL */

	memset(&neg_root, 0, sizeof(neg_root));
	neg_root.udev = &neg_udev;
	neg_root.bus = &neg_bus;
	neg_root.xfer_lock = &neg_xfer_lock;
	lockinit(&neg_xfer_lock, "nxflk", 0, LK_CANRECURSE);

	memset(&neg_xfer, 0, sizeof(neg_xfer));
	neg_xfer.xroot = &neg_root;
	neg_xfer.max_data_length = 1024;	/* USB_EP0_BUFSIZE */
	neg_xfer.max_frame_count = 2;
	neg_xfer.max_frame_size = 64;
	neg_xfer.frlengths = neg_frlengths;
	neg_xfer.frbuffers = neg_pc;
	neg_xfer.local_buffer = neg_ep0buf.base;
}

static usb_handle_req_t *orig_get_desc_p;

static int
neghr_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		if (gbuf_alloc(&neg_ep0buf, 8 + 1024)) {
			kprintf("NEG: alloc ep0 failed\n");
			return (ENOMEM);
		}
		if (gbuf_alloc(&prov_buf, 8192)) {
			gbuf_free(&neg_ep0buf);
			return (ENOMEM);
		}
		neg_setup_graph();

		orig_get_desc_p = usb_temp_get_desc_p;
		usb_temp_get_desc_p = &neg_provider;

		/* bring up the bare-kobj sub-dev for the devmethod leg */
		neghr_buflen = NEGBUF_LEN;
		memset(&neg_fdev, 0, sizeof(neg_fdev));
		kobj_init((kobj_t)&neg_fdev, (kobj_class_t)&neghr_driver);
		neg_fdev.state = DS_ATTACHED;
		neg_fdev.softc = &neg_sc;
		neg_fdev.unit = 0;
		neg_ifaces[0].subdev = (device_t)&neg_fdev;
		g_have_drv = 1;

		kprintf("NEG: rig loaded (production usb_handle_request.c, "
		    "driver-leg=%d)\n", g_have_drv);
		canary_test();
		battery();
		kprintf("NEG: SUMMARY: contained=%d failed=%d "
		    "latent-primitives=%d\n", g_pass, g_fail, g_esc);
		if (g_fail == 0 && g_pass > 0)
			kprintf("NEG: RESULT: ALL CONTAINMENT EXPECTATIONS "
			    "MET (g_fail=0, latent primitives observed as "
			    "expected: %d)\n", g_esc);
		else
			kprintf("NEG: RESULT: CONTAINMENT FAILURES PRESENT "
			    "(g_fail=%d)\n", g_fail);
		return (0);

	case MOD_UNLOAD:
		neg_ifaces[0].subdev = NULL;
		kobj_delete((kobj_t)&neg_fdev, NULL);
		usb_temp_get_desc_p = orig_get_desc_p;
		gbuf_free(&neg_ep0buf);
		gbuf_free(&prov_buf);
		gbuf_free(&neg_sc.buf);
		kprintf("NEG: rig unloaded\n");
		return (0);
	}
	return (EOPNOTSUPP);
}

static moduledata_t negusbhr_mod = {
	"negusbhr",
	&neghr_modevent,
	NULL
};

DECLARE_MODULE(negusbhr, negusbhr_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
