/*
 * DF-1326 pass-2 kernel replica — verbatim prologue of mpr_user_pass_thru()
 *
 * sys/dev/raid/mpr/mpr_user.c:739-807 is reproduced VERBATIM below (the
 * statements and the full local declaration set, in source order), with the
 * driver environment stubbed:
 *
 *   - mpr_softc -> struct fake_sc carrying only mpr_flags (MPR_FLAGS_BUSY)
 *   - mpr_lock/mpr_unlock/mpr_dprint/mpr_printf -> no-op stubs
 *   - copyin/copyout/devfs/ioctl plumbing: REAL kernel primitives
 *
 * What this proves on the real kernel, with the real copyin():
 *   1. the copyin at mpr_user.c:800 happens BEFORE the RequestSize bounds
 *      check at mpr_user.c:804, with a fully user-controlled 32-bit length;
 *   2. RequestSize > 12 (sizeof MPI2_REQUEST_HEADER) writes attacker bytes
 *      past tmphdr on the kernel stack;
 *   3. the saved return-address slot of this frame is within reach of a
 *      96-byte (== reqframesz, passes the post-hoc check too) or 4096-byte
 *      RequestSize, and is fully attacker-controlled before we restore it.
 *
 * The device node is created 0640 root:operator exactly like make_dev() in
 * mpr_attach_user() (mpr_user.c:205) so the privilege boundary being crossed
 * is the same one (operator group -> kernel) as the real /dev/mprN.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioccom.h>
#include <sys/endian.h>
#include <sys/uio.h>

/* ---------------- verbatim type/constant excerpts ---------------- */

/* sys/dev/raid/mpr/mpi/mpi2_type.h */
typedef unsigned char	U8;
typedef unsigned short	U16;
typedef unsigned int	U32;

/* sys/dev/raid/mpr/mpi/mpi2.h:855 — 12 bytes */
typedef struct _MPI2_REQUEST_HEADER
{
    U16             FunctionDependent1;         /* 0x00 */
    U8              ChainOffset;                /* 0x02 */
    U8              Function;                   /* 0x03 */
    U16             FunctionDependent2;         /* 0x04 */
    U8              FunctionDependent3;         /* 0x06 */
    U8              MsgFlags;                   /* 0x07 */
    U8              VP_ID;                      /* 0x08 */
    U8              VF_ID;                      /* 0x09 */
    U16             Reserved1;                  /* 0x0A */
} MPI2_REQUEST_HEADER;

typedef struct _MPI2_DEFAULT_REPLY
{
    U16             FunctionDependent1;
    U8              MsgLength;
    U8              Function;
    U16             FunctionDependent2;
    U8              MsgFlags;
    U8              VP_ID;
    U8              VF_ID;
    U16             Reserved1;
} MPI2_DEFAULT_REPLY;

/* mprvar.h:303-305 */
#define MPR_FLAGS_BUSY		(1 << 2)

/* mpr_ioctl.h:183-200 */
#define MPR_PASS_THRU_DIRECTION_NONE	0
#define MPR_PASS_THRU_DIRECTION_READ	1
#define MPR_PASS_THRU_DIRECTION_WRITE	2
#define MPR_PASS_THRU_DIRECTION_BOTH	3

typedef struct mpr_pass_thru
{
	uint64_t	PtrRequest;
	uint64_t	PtrReply;
	uint64_t	PtrData;
	uint32_t	RequestSize;
	uint32_t	ReplySize;
	uint32_t	DataSize;
	uint32_t	DataDirection;
	uint64_t	PtrDataOut;
	uint32_t	DataOutSize;
	uint32_t	Timeout;
} mpr_pass_thru_t;

/* mpr_ioctl.h:371 */
#define	MPTIOCTL_PASS_THRU	_IOWR('I', 4, struct mpr_pass_thru)

/* ---------------- driver-environment stubs ---------------- */

struct fake_sc {
	int	mpr_flags;
	/* the real softc's reqframesz is IOCRequestFrameSize*4 (mpr.c:387);
	 * SAS3008-class FW reports IOCRequestFrameSize=32 dwords -> 128 bytes */
	u_int	reqframesz;
};

static struct fake_sc g_sc = { 0, 128 };

static __inline void mpr_lock(struct fake_sc *sc) { }
static __inline void mpr_unlock(struct fake_sc *sc) { }
#define mpr_dprint(sc, lvl, ...) do { } while (0)
#define mpr_printf(sc, ...)	kprintf(__VA_ARGS__)

/* ---------------- the verbatim prologue ---------------- */

static int
mpr_user_pass_thru_replica(struct fake_sc *sc, mpr_pass_thru_t *data)
{
	/* ===== VERBATIM local block from mpr_user.c:742-750 ===== */
	MPI2_REQUEST_HEADER	*hdr = NULL, tmphdr;
	MPI2_DEFAULT_REPLY	*rpl = NULL;
	void *nvme_error_reply_v = NULL;
	void *nvme_encap_request_v = NULL;
	struct fake_cmd { char x[64]; } fake_cm_store, *cm = NULL;
	int			i = 0, err = 0, dir = 0, sz;
	uint8_t			tool = 0, function = 0;
	u_int			sense_len = 0;
	void			*targ = NULL;
	memset(&fake_cm_store, 0, sizeof(fake_cm_store));

	/* measurement scaffolding (NOT part of the original frame logic) */
	unsigned long	attacker_tag = 0x4142434445464748UL;
	unsigned long	*saved_rip_slot;
	unsigned long	orig_rip;
	int		rip_off, n_oob;
	unsigned char	*p;
	int		reqsize_capture = (int)data->RequestSize;

	saved_rip_slot = (unsigned long *)__builtin_frame_address(0) + 1;
	orig_rip = *saved_rip_slot;

	kprintf("df1326: enter  reqframesz=%u RequestSize=%u sizeof(tmphdr)=%zu\n",
	    sc->reqframesz, data->RequestSize, sizeof(tmphdr));
	kprintf("df1326: &tmphdr=%p saved_rip_slot=%p (offset %zd bytes)\n",
	    (void *)&tmphdr, (void *)saved_rip_slot,
	    (char *)saved_rip_slot - (char *)&tmphdr);

	/* ===== VERBATIM statements mpr_user.c:752-764 ===== */
	/*
	 * Only allow one passthru command at a time.  Use the MPR_FLAGS_BUSY
	 * bit to denote that a passthru is being processed.
	 */
	mpr_lock(sc);
	if (sc->mpr_flags & MPR_FLAGS_BUSY) {
		mpr_unlock(sc);
		return (EBUSY);
	}
	sc->mpr_flags |= MPR_FLAGS_BUSY;
	mpr_unlock(sc);

	/* ===== VERBATIM mpr_user.c:766-788 ===== */
	if (((data->DataSize == 0) &&
	    (data->DataDirection == MPR_PASS_THRU_DIRECTION_NONE)) ||
	    ((data->DataSize != 0) &&
	    ((data->DataDirection == MPR_PASS_THRU_DIRECTION_READ) ||
	    (data->DataDirection == MPR_PASS_THRU_DIRECTION_WRITE) ||
	    ((data->DataDirection == MPR_PASS_THRU_DIRECTION_BOTH) &&
	    (data->DataOutSize != 0))))) {
		if (data->DataDirection == MPR_PASS_THRU_DIRECTION_BOTH)
			data->DataDirection = MPR_PASS_THRU_DIRECTION_READ;
		else
			data->DataOutSize = 0;
	} else
		return (EINVAL);

	/* ===== VERBATIM mpr_user.c:796-807 ===== */
	/*
	 * copy in the header so we know what we're dealing with before we
	 * commit to allocating a command for it.
	 */
#ifdef FIXED
	/* DF-1326 fix.diff applied: bounds-check BEFORE copyin, and copy
	 * only the 12 header bytes we actually peek at here. */
	if (data->RequestSize < (int)sizeof(tmphdr) ||
	    data->RequestSize > (int)sc->reqframesz) {
		err = EINVAL;
		goto RetFreeUnlocked;
	}
	err = copyin((void *)(uintptr_t)data->PtrRequest, &tmphdr,
	    sizeof(tmphdr));
#else
	err = copyin((void *)(uintptr_t)data->PtrRequest, &tmphdr,
	    data->RequestSize);				/* <-- line 800 */
#endif
	if (err != 0) {
		kprintf("df1326: copyin err=%d (RequestSize=%u)\n", err,
		    data->RequestSize);
		goto RetFreeUnlocked;
	}

	/*
	 * measurement: quantify the smash that the real driver has already
	 * suffered at this point, then restore our own frame so the module
	 * can keep running.  The real driver has NO such restoration.
	 */
	rip_off = (int)((char *)saved_rip_slot - (char *)&tmphdr);
	n_oob = 0;
	for (p = (unsigned char *)&tmphdr + sizeof(tmphdr);
	     p < (unsigned char *)&tmphdr + reqsize_capture; p++)
		if (*(unsigned long *)p == attacker_tag)
			n_oob += (int)sizeof(unsigned long);
	kprintf("df1326: copyin returned 0 -- %d bytes landed past the "
	    "%zu-byte tmphdr\n", reqsize_capture - (int)sizeof(tmphdr),
	    sizeof(tmphdr));
	if (rip_off > 0 && rip_off < reqsize_capture) {
		kprintf("df1326: SAVED RIP SLOT OVERWRITTEN: was %p now %p "
		    "(attacker-controlled: %s)\n", (void *)orig_rip,
		    (void *)*saved_rip_slot,
		    *saved_rip_slot == attacker_tag ? "YES, exact 8-byte "
		    "attacker value" : "YES");
		*saved_rip_slot = orig_rip;	/* keep this module alive */
		kprintf("df1326: (restored; the real driver would return "
		    "through the attacker value)\n");
	} else {
		kprintf("df1326: saved RIP slot out of reach for this size "
		    "(offset %d)\n", rip_off);
	}
	kprintf("df1326: %lu-byte aligned attacker-pattern spans observed "
	    "out-of-band\n", n_oob / (long)sizeof(unsigned long));

	if (data->RequestSize > (int)sc->reqframesz) {	/* <-- line 804 */
		err = EINVAL;
		goto RetFreeUnlocked;
	}
	kprintf("df1326: RequestSize %u PASSED the (post-copyin) bounds "
	    "check too\n", data->RequestSize);

	/* control-flow marker so the compiler keeps all locals alive */
	function = tmphdr.Function;
	sz = (int)function + i + dir + (int)sense_len + (int)(uintptr_t)targ +
	    (int)(uintptr_t)rpl + (int)(uintptr_t)hdr +
	    (int)fake_cm_store.x[0] + (int)(uintptr_t)nvme_error_reply_v +
	    (int)(uintptr_t)nvme_encap_request_v + (int)(uintptr_t)cm +
	    (int)tool;
	kprintf("df1326: prologue complete (dead-code checksum %d)\n", sz);

RetFreeUnlocked:
	sc->mpr_flags &= ~MPR_FLAGS_BUSY;
	return (err);
}

/* ---------------- devfs plumbing mirroring mpr_user.c:104-113/199-236 ---- */

static d_open_t	df1326_open;
static d_close_t df1326_close;
static d_ioctl_t df1326_ioctl_devsw;

static struct dev_ops df1326_ops = {
	{ "df1326", 0, D_MPSAFE },
	.d_open =	df1326_open,
	.d_close =	df1326_close,
	.d_ioctl =	df1326_ioctl_devsw,
};

static int
df1326_open(struct dev_open_args *ap)
{
	return (0);
}

static int
df1326_close(struct dev_close_args *ap)
{
	return (0);
}

static int
df1326_ioctl(struct cdev *dev, u_long cmd, void *arg, int flag)
{
	switch (cmd) {
	case MPTIOCTL_PASS_THRU:
		/* mpr_ioctl() mpr_user.c:2322-2329 shape */
		return (mpr_user_pass_thru_replica(&g_sc,
		    (mpr_pass_thru_t *)arg));
	default:
		return (ENOIOCTL);
	}
}

static int
df1326_ioctl_devsw(struct dev_ioctl_args *ap)
{
	return (df1326_ioctl(ap->a_head.a_dev, ap->a_cmd, ap->a_data,
	    ap->a_fflag));
}

static int
df1326_modevent(module_t mod, int type, void *data)
{
	static cdev_t cdev;

	switch (type) {
	case MOD_LOAD:
		/* make_dev args verbatim from mpr_attach_user() 205-206 */
		cdev = make_dev(&df1326_ops, 0, UID_ROOT, GID_OPERATOR, 0640,
		    "df1326");
		if (cdev == NULL)
			return (ENOMEM);
		reference_dev(cdev);
		kprintf("df1326: replica loaded (MPI2_REQUEST_HEADER=%zu "
		    "bytes, no canary in kernel: see nm(1))\n",
		    sizeof(MPI2_REQUEST_HEADER));
		return (0);
	case MOD_UNLOAD:
		destroy_dev(cdev);
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

DEV_MODULE(df1326, df1326_modevent, NULL);
