/*
 * DF-5455 KLD harness -- production-code demonstration of the umoscom
 * (MosChip MCS7703) uninitialized-stack read on failed/short vendor
 * control-IN, and its flow into the TIOCMGET modem-status bits.
 *
 * WHAT IT PROVES:
 *
 *   umoscom_cfg_read() (umoscom.c:539-557) declares `uint8_t val;`
 *   UNINITIALIZED, issues a 1-byte vendor control-IN through
 *   ucom_cfg_do_request() (usb_serial.h:220-221 -> usbd_do_request_proc,
 *   actlen hard-coded NULL) and RETURNS val REGARDLESS of the
 *   usb_error_t result, which is discarded.
 *
 *   Core semantics (traced, usb_request.c):
 *     - timeout / stall / cancel: the WXFER loop breaks on
 *       xfer->error BEFORE the usbd_copy_out() at :680-681 -- *ptr is
 *       never written (usb_request.c:642-650);
 *     - a "successful" control-IN with a zero-length data stage copies
 *       nothing (temp == 0 at :666-683) -- *ptr never written;
 *     - a short (0-byte of 1-byte) data stage with short_xfer_ok NOT set
 *       (umoscom passes flags=0) completes with USB_ERR_SHORT_XFER ->
 *       same no-copy break.
 *   In ALL of those cases umoscom_cfg_read() returns one byte of stale
 *   kernel stack.
 *
 *   The byte has a live consumer, unlike the uvscom DF-5448 sibling:
 *     umoscom_cfg_get_status() (umoscom.c:495-522)
 *       msr = umoscom_cfg_read(sc, UMOSCOM_MSR);   <-- :507
 *       if (msr & UMOSCOM_MSR_CTS) *p_msr |= SER_CTS;  <-- :511-512
 *       if (msr & UMOSCOM_MSR_CD)  *p_msr |= SER_DCD;  <-- :514-515
 *       if (msr & UMOSCOM_MSR_RI)  *p_msr |= SER_RI;   <-- :517-518
 *       if (msr & UMOSCOM_MSR_RTS) *p_msr |= SER_DSR;  <-- :520-521
 *     -> ucom_cfg_status_change() stores it: sc->sc_msr = new_msr
 *        (usb_serial.c:1527)
 *     -> ucom_modem()/TIOCMGET: if (sc->sc_msr & SER_CTS) sigon |= SER_CTS
 *        ... (usb_serial.c:1321-1332) -> ucom_totio() maps to
 *        TIOCM_CTS/TIOCM_CD/TIOCM_RI/TIOCM_DSR (usb_serial.c:1259-1270)
 *        and TIOCMGET copies the int out to userspace
 *        (usb_serial.c:1209-1211).
 *   => bits 4..7 of one uninitialized kernel-stack byte are disclosed to
 *      an unprivileged TIOCMGET reader, 4 bits per status refresh, on
 *      every control-read failure (evil device: just stay silent on the
 *      MSR vendor read; flaky device: timeout/stall).
 *
 * HOW IT RUNS PRODUCTION CODE:
 *
 *   This module #includes "umoscom_prod_guarded.c" -- a byte-identical
 *   copy of /usr/src/sys/bus/u4b/serial/umoscom.c except ONE guard hunk
 *   (prod_guard.diff in this pack): the ucom_cfg_do_request macro is
 *   rerouted AFTER its usb_serial.h definition to mo_shim_request(),
 *   which reproduces the traced failure semantics above by NOT touching
 *   the caller's buffer.  Everything else -- the DRIVER_MODULE
 *   registration (inert: no MCS7703 exists on the guest, so nothing ever
 *   probes/attaches) included -- is compiled verbatim, and every
 *   production function, umoscom_cfg_read() and umoscom_cfg_get_status()
 *   among them, is called directly.
 *
 *   The stack slot is made deterministic with a recursive poison frame
 *   (mo_poison) called at the same depth right before the production
 *   chain, mimicking stale stack contents from prior ucom-task work
 *   (termios copies, request buffers, pointers).
 *
 *   Counterfactual: mo_cfg_read_fixed() is the same function with the
 *   ONE-LINE FIX (`uint8_t val = 0;`) -- under identical poisoning it
 *   must report zero SER bits.
 */
#include <sys/param.h>

#define	UMOSCOM_HARNESS 1
#include "umoscom_prod_guarded.c"

/*
 * Shim state (the shim body itself is in mo5455_shim.c -- a SEPARATE
 * compilation unit, exactly like production's usbd_do_request_proc:
 * as long as the compiler cannot see that *ptr is never written, the
 * uninitialized read in umoscom_cfg_read() compiles to a real stack
 * load.  When the shim was in the same TU, GCC -O2 folded the
 * provably-uninitialized load to a constant 0 -- run.1.fold.log in this
 * pack documents that artifact.  The -Werror=uninitialized diagnostic
 * fired either way (build.werror_evidence.log).
 *
 * Shim modes:
 *   mode 0: data/status stage never answered -> USB_ERR_TIMEOUT
 *           (usb_request.c:642-650: break BEFORE the copy-out)
 *   mode 1: zero-length data stage, status stage OK -> SUCCESS with
 *           nothing copied (usb_request.c:666-683)
 * Both leave *ptr untouched.  The returned usb_error_t is discarded by
 * production umoscom_cfg_read() either way (umoscom.c:551-556).
 */
extern int	mo_shim_mode;
extern int	mo_shim_requests;

static struct umoscom_softc mo_sc;		/* zeroed: softc stand-in */
static struct ucom_softc mo_ucom;		/* sc_parent wired to mo_sc */

static uint8_t	mo_msr_out;			/* SER_* bits observed */
static uint8_t	mo_lsr_out;

/*
 * The production call chain at depth D (what ucom_cfg_status_change,
 * usb_serial.c:1515-1518, does -- new_msr/new_lsr start at 0).
 */
static void __noinline
mo_prod_status(void)
{
	uint8_t lsr = 0;		/* usb_serial.c:1516 */
	uint8_t msr = 0;		/* usb_serial.c:1515 */

	umoscom_cfg_get_status(&mo_ucom, &lsr, &msr);	/* PRODUCTION */

	mo_lsr_out = lsr;
	mo_msr_out = msr;
}

/*
 * Counterfactual: verbatim umoscom_cfg_read() + umoscom_cfg_get_status()
 * bodies with the one-line fix applied (`uint8_t val = 0;`).
 */
static uint8_t __noinline
mo_cfg_read_fixed(struct umoscom_softc *sc, uint16_t reg)
{
	struct usb_device_request req;
	uint8_t val = 0;		/* <-- THE FIX */

	req.bmRequestType = UT_READ_VENDOR_DEVICE;
	req.bRequest = UMOSCOM_READ;
	USETW(req.wValue, 0);
	USETW(req.wIndex, reg);
	USETW(req.wLength, 1);

	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
	    &req, &val, 0, 1000);

	return (val);
}

static void __noinline
mo_prod_status_fixed(void)
{
	uint8_t lsr = 0;
	uint8_t msr = 0;
	uint8_t v;

	v = mo_cfg_read_fixed(&mo_sc, UMOSCOM_LSR);	/* value discarded */
	v = mo_cfg_read_fixed(&mo_sc, UMOSCOM_MSR);

	if (v & UMOSCOM_MSR_CTS)
		msr |= SER_CTS;
	if (v & UMOSCOM_MSR_CD)
		msr |= SER_DCD;
	if (v & UMOSCOM_MSR_RI)
		msr |= SER_RI;
	if (v & UMOSCOM_MSR_RTS)
		msr |= SER_DSR;

	mo_lsr_out = lsr;
	mo_msr_out = msr;
}

/*
 * Deterministic stale-stack stand-in: scribble a known pattern over the
 * stack region the production frames will reuse.
 */
static void __noinline
mo_poison(uint8_t pat, int depth)
{
	volatile uint8_t pad[256];
	int i;

	for (i = 0; i < 256; ++i)
		pad[i] = pat;
	if (depth > 0)
		mo_poison(pat, depth - 1);
}

/* SER_* -> TIOCM_* exactly as ucom_totio() (usb_serial.c:1246-1273) */
static int
mo_totio(uint8_t ser)
{
	int rbits = 0;

	SET(rbits, TIOCM_LE);
	if (ISSET(ser, SER_DTR))
		SET(rbits, TIOCM_DTR);
	if (ISSET(ser, SER_RTS))
		SET(rbits, TIOCM_RTS);
	if (ISSET(ser, SER_CTS))
		SET(rbits, TIOCM_CTS);
	if (ISSET(ser, SER_DCD))
		SET(rbits, TIOCM_CD);
	if (ISSET(ser, SER_DSR))
		SET(rbits, TIOCM_DSR);
	if (ISSET(ser, SER_RI))
		SET(rbits, TIOCM_RI);
	return (rbits);
}

static uint8_t
mo_predict_ser(uint8_t pat)
{
	uint8_t ser = 0;

	/* the four umoscom_cfg_get_status translations, umoscom.c:511-521 */
	if (pat & UMOSCOM_MSR_CTS)
		ser |= SER_CTS;
	if (pat & UMOSCOM_MSR_CD)
		ser |= SER_DCD;
	if (pat & UMOSCOM_MSR_RI)
		ser |= SER_RI;
	if (pat & UMOSCOM_MSR_RTS)
		ser |= SER_DSR;
	return (ser);
}

static int
mo_run_once(uint8_t pat)
{
	int match = 0;

	mo_shim_requests = 0;
	mo_shim_mode = 0;		/* silent device: timeout */
	mo_poison(pat, 6);
	mo_prod_status();
	kprintf("MO5455: pat=0x%02x mode=timeout  : PROD msr=0x%02x "
	    "(predict 0x%02x) TIOCM after ucom_totio=0%o %s\n",
	    pat, mo_msr_out, mo_predict_ser(pat), mo_totio(mo_msr_out),
	    mo_msr_out == mo_predict_ser(pat) ? "MATCH" : "MISMATCH");
	if (mo_msr_out == mo_predict_ser(pat))
		match++;

	mo_shim_mode = 1;		/* zero-length data stage */
	mo_poison(pat, 6);
	mo_prod_status();
	kprintf("MO5455: pat=0x%02x mode=0-len    : PROD msr=0x%02x "
	    "(predict 0x%02x) TIOCM after ucom_totio=0%o %s\n",
	    pat, mo_msr_out, mo_predict_ser(pat), mo_totio(mo_msr_out),
	    mo_msr_out == mo_predict_ser(pat) ? "MATCH" : "MISMATCH");
	if (mo_msr_out == mo_predict_ser(pat))
		match++;

	/* counterfactual with the one-line fix */
	mo_shim_mode = 0;
	mo_poison(pat, 6);
	mo_prod_status_fixed();
	kprintf("MO5455: pat=0x%02x FIXED (val=0) : msr=0x%02x %s\n",
	    pat, mo_msr_out,
	    mo_msr_out == 0 ? "(clean)" : "(LEAK SURVIVES FIX?!)");

	kprintf("MO5455:   (2 control reads consumed per status call; "
	    "shim saw %d requests)\n", mo_shim_requests);
	return (match);
}

static int
mo_modevent(module_t mod, int type, void *data)
{
	static const uint8_t pats[] = { 0x00, 0xff, 0xa5, 0x5a, 0x0f, 0xf0 };
	int i, matches = 0, total = 0;
	int error = 0;

	switch (type) {
	case MOD_LOAD:
		mo_ucom.sc_parent = &mo_sc;

		kprintf("MO5455: DF-5455 umoscom uninit-read harness: "
		    "production umoscom.c compiled in "
		    "(guarded copy, 1 hunk, see prod_guard.diff)\n");
		for (i = 0; i < (int)(sizeof(pats)/sizeof(pats[0])); ++i) {
			matches += mo_run_once(pats[i]);
			total += 2;
		}
		kprintf("MO5455: RESULT: production umoscom_cfg_read -> "
		    "cfg_get_status -> SER/TIOCM bits: %d/%d pattern-match "
		    "across failure modes%s\n", matches, total,
		    matches == total ? " -- UNINITIALIZED STACK DISCLOSED "
		    "THROUGH MODEM-STATUS BITS" : "");
		break;
	case MOD_UNLOAD:
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}

static moduledata_t mo5455_mod = { "umoscomunleak", mo_modevent, 0 };
DECLARE_MODULE(umoscomunleak, mo5455_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(umoscomunleak, ucom, 1, 1, 1);
MODULE_DEPEND(umoscomunleak, usb, 1, 1, 1);
