/*
 * DF-4053 trigger — virtio_scsi.c sense memcpy dst/src swap.
 *
 * Sends an out-of-range READ(10) to the vtscsi0 da0/pass0 device so the
 * target returns CHECK CONDITION with a known sense (ILLEGAL REQUEST,
 * ASC 0x21 "LOGICAL BLOCK ADDRESS OUT OF RANGE").
 *
 * On a correct driver the CCB comes back with CAM_AUTOSNS_VALID and
 * sense_data = 70 00 05 00 ... 21 00 ...
 * On the buggy DragonFly driver (virtio_scsi.c:1392-1394 memcpy(dst,src)
 * swapped vs FreeBSD), sense_data is the bzero()ed buffer: all zeros.
 *
 * argv[1] = csio.sense_len (default 255) — demonstrates that a pass(4)
 * CAMIOCOMMAND CCB carries an UNVALIDATED sense_len (u_int8_t, <=255)
 * straight into the driver's length math:
 *     copy_len = csio->sense_len - sense_resid  (== device sense_len
 *               when device sense_len < ccb sense_len)
 * With a malicious virtio-scsi backend returning sense_len >= 97 this
 * copy overflows vsr_uresp.cmd.sense[96] in struct vtscsi_request.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/scsi/scsi_message.h>
#include <camlib.h>

static void
hexdump(const unsigned char *p, int len)
{
	int i;

	for (i = 0; i < len; i++) {
		if (i % 16 == 0)
			printf("  %02x:", i);
		printf(" %02x", p[i]);
		if (i % 16 == 15)
			printf("\n");
	}
	if (len % 16)
		printf("\n");
}

int
main(int argc, char **argv)
{
	struct cam_device *dev;
	union ccb ccb;
	unsigned char data[512];
	int sense_len = (argc > 1) ? atoi(argv[1]) : 255;
	int i;

	dev = cam_open_device("/dev/pass0", O_RDWR);
	if (dev == NULL) {
		fprintf(stderr, "cam_open_device: %s\n", cam_errbuf);
		return (1);
	}

	memset(&ccb, 0, sizeof(ccb));
	memset(data, 0, sizeof(data));

	/* READ(10) LBA=0xfffffff0 (out of range on a 64MB disk), 1 block */
	ccb.ccb_h.func_code = XPT_SCSI_IO;
	ccb.ccb_h.path_id = dev->path_id;
	ccb.ccb_h.target_id = dev->target_id;
	ccb.ccb_h.target_lun = dev->target_lun;
	ccb.ccb_h.timeout = 5000;
	ccb.ccb_h.flags = CAM_DIR_IN;
	ccb.csio.tag_action = MSG_SIMPLE_Q_TAG;
	ccb.csio.cdb_len = 10;
	ccb.csio.data_ptr = data;
	ccb.csio.dxfer_len = sizeof(data);
	ccb.csio.sense_len = (sense_len > 0 && sense_len <= 255) ?
	    sense_len : 255;
	ccb.csio.cdb_io.cdb_bytes[0] = 0x28;		/* READ(10) */
	ccb.csio.cdb_io.cdb_bytes[2] = 0xff;
	ccb.csio.cdb_io.cdb_bytes[3] = 0xff;
	ccb.csio.cdb_io.cdb_bytes[4] = 0xff;
	ccb.csio.cdb_io.cdb_bytes[5] = 0xf0;
	ccb.csio.cdb_io.cdb_bytes[8] = 0x01;

	printf("sending out-of-range READ(10) with csio.sense_len=%u\n",
	    ccb.csio.sense_len);

	if (cam_send_ccb(dev, &ccb) < 0) {
		perror("cam_send_ccb");
		return (1);
	}

	printf("ccb_h.status     = 0x%02x%s%s\n", ccb.ccb_h.status,
	    (ccb.ccb_h.status & CAM_AUTOSNS_VALID) ? " CAM_AUTOSNS_VALID" : "",
	    (ccb.ccb_h.status & CAM_STATUS_MASK) == CAM_SCSI_STATUS_ERROR ?
	    " CAM_SCSI_STATUS_ERROR" : "");
	printf("csio.scsi_status = 0x%02x (0x02 = CHECK CONDITION)\n",
	    ccb.csio.scsi_status);
	printf("csio.sense_resid = %u\n", ccb.csio.sense_resid);
	printf("csio.sense_data[0..%d]:\n",
	    ccb.csio.sense_len > 32 ? 31 : ccb.csio.sense_len - 1);
	hexdump((unsigned char *)&ccb.csio.sense_data,
	    ccb.csio.sense_len > 32 ? 32 : ccb.csio.sense_len);

	/*
	 * A valid SCSI sense always starts with error code 0x70 (current)
	 * or 0x71 (deferred). Byte 2 high nibble = sense key (5 = ILLEGAL
	 * REQUEST), byte 12 = ASC (0x21 = LBA out of range).
	 */
	{
		unsigned char *s = (unsigned char *)&ccb.csio.sense_data;
		int bad = (s[0] != 0x70 && s[0] != 0x71);

		printf("VERDICT: sense_data is %s (%s)\n",
		    bad ? "ZEROED/EMPTY — memcpy direction is SWAPPED" :
		    "REAL",
		    bad ? "bug reproduced" : "driver delivered autosense");
		if (!bad)
			printf("  sense key=0x%02x ASC=0x%02x ASCQ=0x%02x\n",
			    s[2] & 0x0f, s[12], s[13]);
	}

	cam_close_device(dev);
	return (0);
}
