⬢ DragonFlyBSD Kernel Audit
DF-5543 / uep_harness.c
← back to finding ↓ download raw
/*
 * uep_harness.c - production-code KLD harness for sys/bus/u4b/input/uep.c
 *
 * Compiles the REAL production file (uep.c, unmodified) into this module
 * with only its module boilerplate neutered and its USB/FIFO externals
 * renamed to deterministic fakes. uep_probe/attach/detach are compiled but
 * never called; the state machine under test (get_pkt_len(),
 * uep_process_pkt(), uep_put_queue(), uep_intr_callback()) is the stock
 * production code.
 *
 * Synthetic interrupt transfers are fed to uep_intr_callback() exactly as
 * the USB layer would (state=USB_ST_TRANSFERRED, actlen=payload length);
 * uep_put_queue()'s usb_fifo_put_data_linear() calls (the only path from
 * the parser into the /dev/uep0 FIFO, 5 bytes per report, what=1 framing)
 * are captured in a shadow FIFO log.
 *
 * Scenarios:
 *   T1  control      - aligned 3x5-byte report stream -> 3 reports queued
 *   T2  stall (A)    - 1-byte transfer with non-report header byte, then
 *                      10 further valid transfers -> 0 reports, buf_len==1
 *                      forever  ==> DF-5542 permanent input stall
 *   T3  stash (B)    - 5-byte partial PRESSURE packet + 2-byte completion
 *                      ending in exactly 1 remaining byte -> wrong byte
 *                      stashed (buf[0] instead of p[0]) -> fabricated
 *                      report, true byte dropped  ==> DF-5543
 *   T4  cascade (B->A) - same as T3 but mis-stashed byte is a bad header
 *                      -> permanent stall  ==> DF-5543 feeding DF-5542
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/lock.h>
#include <sys/bus.h>

#include <bus/u4b/usb.h>
#include <bus/u4b/usbdi.h>
#include <bus/u4b/usbdi_util.h>
#include <bus/u4b/usbhid.h>

/* ------------------------------------------------------------------ */
/* Fakes for the USB/FIFO externals the production callback touches.   */
/* ------------------------------------------------------------------ */

static struct uep_softc *g_sc;		/* filled after uep.c is included */
static u_char  g_epdata[64];
static int     g_actlen;
static uint8_t g_state;

/* shadow FIFO: what uep_put_queue() would vend to /dev/uep0 readers */
static u_char  shadow_fifo[256];
static size_t  shadow_len;
static int     put_calls;
static int     submit_cnt;

static void *
fake_usbd_xfer_softc(struct usb_xfer *xfer)
{
	return (g_sc);
}

static void
fake_usbd_xfer_status(struct usb_xfer *xfer, int *actlen, int *sumlen,
    int *aframes, int *nframes)
{
	if (actlen)
		*actlen = g_actlen;
}

static uint8_t
fake_usbd_xfer_state(struct usb_xfer *xfer)
{
	return (g_state);
}

static struct usb_page_cache *
fake_usbd_xfer_get_frame(struct usb_xfer *xfer, usb_frcount_t frindex)
{
	return ((struct usb_page_cache *)g_epdata);
}

static void
fake_usbd_copy_out(struct usb_page_cache *pc, usb_frlength_t offset,
    void *dst, usb_frlength_t len)
{
	memcpy(dst, (const char *)pc + offset, len);
}

static uint32_t
fake_usb_fifo_put_bytes_max(struct usb_fifo *fifo)
{
	return (8);		/* free mbuf available: allow resubmit */
}

static void
fake_usb_fifo_put_data_linear(struct usb_fifo *fifo, void *ptr,
    usb_size_t len, uint8_t what)
{
	if (shadow_len + len > sizeof(shadow_fifo))
		len = 0;	/* cannot happen in our scenarios */
	memcpy(shadow_fifo + shadow_len, ptr, len);
	shadow_len += len;
	put_calls++;
	/* what==1: one 5-byte report record, same framing as the real FIFO */
}

static void
fake_usbd_xfer_set_frame_len(struct usb_xfer *xfer, usb_frcount_t frindex,
    usb_frlength_t len)
{
}

static void
fake_usbd_transfer_submit(struct usb_xfer *xfer)
{
	submit_cnt++;
}

static usb_frlength_t
fake_usbd_xfer_max_len(struct usb_xfer *xfer)
{
	return (16);
}

static void
fake_usbd_xfer_set_stall(struct usb_xfer *xfer)
{
}

/* ------------------------------------------------------------------ */
/* Pull in the production driver.                                      */
/* ------------------------------------------------------------------ */

#undef  DRIVER_MODULE
#undef  MODULE_DEPEND
#undef  MODULE_VERSION
#define DRIVER_MODULE(...)	/* neutered: harness is not a uep driver */
#define MODULE_DEPEND(...)
#define MODULE_VERSION(...)

#define usbd_xfer_softc		fake_usbd_xfer_softc
#define usbd_xfer_status	fake_usbd_xfer_status
#define usbd_xfer_state		fake_usbd_xfer_state
#define usbd_xfer_get_frame	fake_usbd_xfer_get_frame
#define usbd_copy_out		fake_usbd_copy_out
#define usb_fifo_put_bytes_max	fake_usb_fifo_put_bytes_max
#define usb_fifo_put_data_linear fake_usb_fifo_put_data_linear
#define usbd_xfer_set_frame_len fake_usbd_xfer_set_frame_len
#define usbd_transfer_submit	fake_usbd_transfer_submit
#define usbd_xfer_max_len	fake_usbd_xfer_max_len
#define usbd_xfer_set_stall	fake_usbd_xfer_set_stall

#include "uep.c"

/* keep the neutered module boilerplate symbols referenced (-Werror) */
static void *ueph_keepalive[] __unused = { &uep_driver, &uep_devclass };

/* ------------------------------------------------------------------ */
/* Scenario driver.                                                    */
/* ------------------------------------------------------------------ */

static void
harness_reset(void)
{
	if (g_sc != NULL) {
		lockuninit(&g_sc->lock);
		kfree(g_sc, M_TEMP);
	}
	g_sc = kmalloc(sizeof(struct uep_softc), M_TEMP, M_INTWAIT | M_ZERO);
	lockinit(&g_sc->lock, "uep lock", 0, 0);
	g_sc->buf_len = 0;			/* as uep_attach() line 338 */

	memset(shadow_fifo, 0, sizeof(shadow_fifo));
	shadow_len = 0;
	put_calls = 0;
	submit_cnt = 0;
}

static void
fire(const u_char *d, int len)
{
	memcpy(g_epdata, d, len);
	g_actlen = len;
	g_state = USB_ST_TRANSFERRED;
	uep_intr_callback(NULL, 0);	/* xfer ptr only reaches the fakes */
}

static const u_char valid15[15] = {
	0x81, 0x11, 0x22, 0x33, 0x44,		/* report 1 */
	0x81, 0x55, 0x66, 0x77, 0x88,		/* report 2 */
	0x81, 0x99, 0xaa, 0xbb, 0xcc,		/* report 3 */
};

static int
count_reports(void)
{
	return (shadow_len / UEP_PACKET_LEN_REPORT);
}

static void
run_scenarios(void)
{
	int i;

	kprintf("UEPH: harness start, production uep.c compiled in\n");

	/* T1: control - aligned stream of 3 reports */
	harness_reset();
	fire(valid15, 15);
	kprintf("UEPH: T1 control queued_reports=%d queued_bytes=%zu "
	    "buf_len=%u submits=%d\n",
	    count_reports(), shadow_len, g_sc->buf_len, submit_cnt);

	/* T2: DF-5542 - 1-byte transfer with a non-report header byte */
	harness_reset();
	fire((const u_char[]){ 0x00 }, 1);	/* bad header stashed */
	for (i = 0; i < 10; i++)
		fire(valid15, 15);		/* valid data forever after */
	kprintf("UEPH: T2 stall queued_reports=%d queued_bytes=%zu "
	    "buf_len=%u submits=%d verdict=%s\n",
	    count_reports(), shadow_len, g_sc->buf_len, submit_cnt,
	    (count_reports() == 0 && g_sc->buf_len == 1) ?
	    "PERMANENT_STALL" : "no_stall");

	/* T3: DF-5543 - wrong byte stashed when 1 byte remains after
	 * completing a reassembled 6-byte PRESSURE packet */
	harness_reset();
	fire((const u_char[]){ 0xc0, 0x01, 0x02, 0x03, 0x04 }, 5);
	fire((const u_char[]){ 0x81, 0xAA }, 2);	/* 0x81 completes the
							 * pressure packet,
							 * 0xAA is the true
							 * remaining byte */
	fire((const u_char[]){ 0xBB, 0xCC, 0xDD, 0xEE }, 4);
	{
		int aa_dropped = 1;
		size_t k;
		for (k = 0; k + 5 <= shadow_len; k++)
			if (shadow_fifo[k] == 0xAA)
				aa_dropped = 0;
		kprintf("UEPH: T3 stash queued_reports=%d report0="
		    "%02x:%02x:%02x:%02x:%02x aa_dropped=%d verdict=%s\n",
		    count_reports(),
		    shadow_fifo[0], shadow_fifo[1], shadow_fifo[2],
		    shadow_fifo[3], shadow_fifo[4], aa_dropped,
		    (shadow_len == 5 && shadow_fifo[0] == 0x81 &&
		    aa_dropped) ? "WRONG_BYTE_STASHED" : "correct");
	}

	/* T4: DF-5543 feeding DF-5542 - mis-stashed byte is a bad header */
	harness_reset();
	fire((const u_char[]){ 0xc0, 0x01, 0x02, 0x03, 0x04 }, 5);
	fire((const u_char[]){ 0x55, 0x81 }, 2);	/* 0x55 (consumed byte,
							 * bad header) stashed
							 * instead of 0x81 */
	for (i = 0; i < 5; i++)
		fire(valid15, 15);
	kprintf("UEPH: T4 cascade queued_reports=%d queued_bytes=%zu "
	    "buf_len=%u verdict=%s\n",
	    count_reports(), shadow_len, g_sc->buf_len,
	    (count_reports() == 0 && g_sc->buf_len == 1) ?
	    "PERMANENT_STALL" : "no_stall");

	kprintf("UEPH: harness done\n");
}

static int
ueph_modevent(module_t mod, int type, void *arg)
{
	switch (type) {
	case MOD_LOAD:
		run_scenarios();
		return (0);
	case MOD_UNLOAD:
		if (g_sc != NULL) {
			lockuninit(&g_sc->lock);
			kfree(g_sc, M_TEMP);
			g_sc = NULL;
		}
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t ueph_mod = {
	"ueph",
	ueph_modevent,
	NULL
};

DECLARE_MODULE(ueph, ueph_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);