DF-3667 / df3667_sim.c
/* * DF-3667 โ bound-math demonstration harness (USERSPACE SIMULATION, NOT A KERNEL RUN) * * Replicates, line for line, the assignment chain of ix_rxeof() * (sys/dev/netif/ix/if_ix.c): * * len = le16toh(cur->wb.upper.length); // if_ix.c:2974 NIC-DMA'd 16-bit * ... * mp->m_len = len; // if_ix.c:3031 NO bound vs cluster * mp->m_pkthdr.len = len; / += len // if_ix.c:3040/3044 * * and the cluster allocation it must be bounded by: * * m = m_getjcl(flags, MT_DATA, M_PKTHDR, rxr->rx_mbuf_sz); // if_ix.c:4142 * rx_mbuf_sz = MCLBYTES(2048) or MJUMPAGESIZE(4096) // if_ix.c:2635-2638 * * A hostile/compromised NIC (or a peer with control of descriptor DMA, e.g. via * a peripheral DMA attack) writes wb.upper.length = 0xFFFF. The kernel then * hands the network stack an mbuf claiming 65535 valid bytes inside a 2048-byte * cluster: every subsequent reader (ether_demux, ip_input, bpf, forwarding) * reads up to 63487 bytes past the end of the cluster -> kernel heap info leak. * * This program proves the arithmetic with guard pages, using malloc'd clusters * with poisoned redzones. It is evidence for the bound math only; the kernel * path itself requires ixgbe hardware which this guest does not have. * * Build: cc -O2 -o df3667_sim df3667_sim.c * Run: ./df3667_sim */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define MCLBYTES 2048 /* sys/sys/param.h */ #define MJUMPAGESIZE 4096 struct sim_mbuf { uint32_t m_len; /* what rxeof publishes */ uint8_t *data; /* the rx cluster */ size_t clsize; }; /* Simulated ix_newbuf(): one jumbo cluster of rx_mbuf_sz bytes with a * poisoned redzone standing in for adjacent heap slabs. */ static struct sim_mbuf * sim_newbuf(size_t rx_mbuf_sz) { struct sim_mbuf *m = calloc(1, sizeof(*m)); m->clsize = rx_mbuf_sz; m->data = malloc(rx_mbuf_sz + 64); memset(m->data + rx_mbuf_sz, 0xAA, 64); /* poison past the cluster */ return m; } /* Simulated ix_rxeof() length handling (if_ix.c:2974,3031,3040). */ static void sim_rxeof(struct sim_mbuf *m, uint16_t desc_len) { m->m_len = desc_len; /* if_ix.c:3031 โ no bound check */ } /* Simulated consumer: ether/ip parse reading m_len bytes from cluster. */ static size_t sim_consume_oob(struct sim_mbuf *m) { size_t oob = 0, i; for (i = m->clsize; i < m->m_len && i < m->clsize + 64; i++) if (m->data[i] == 0xAA) oob++; /* bytes that are NOT ours but we'd hand out */ if (m->m_len > m->clsize + 64) oob += m->m_len - (m->clsize + 64); return oob; } int main(void) { struct sim_mbuf *m2k = sim_newbuf(MCLBYTES); struct sim_mbuf *m4k = sim_newbuf(MJUMPAGESIZE); uint16_t hostile_len = 0xFFFF; /* max 16-bit descriptor length */ sim_rxeof(m2k, hostile_len); sim_rxeof(m4k, hostile_len); printf("DF-3667 ix_rxeof descriptor-length trust โ bound math\n"); printf("cluster(MCLBYTES)=2048 desc len=0x%04x claimed m_len=%u OOB read span=%zu bytes\n", hostile_len, m2k->m_len, (size_t)m2k->m_len - m2k->clsize); printf("cluster(MJUMPAGESIZE)=4096 desc len=0x%04x claimed m_len=%u OOB read span=%zu bytes\n", hostile_len, m4k->m_len, (size_t)m4k->m_len - m4k->clsize); printf("redzone bytes a consumer would hand to the stack (2K cluster): %zu\n", sim_consume_oob(m2k)); printf("m_pkthdr.len accumulator (single desc): %u (chain adds per desc, if_ix.c:3044)\n", m2k->m_len); /* Even a modest lie (3000 into a 2048 cluster โ e.g. SRRCTL/erratum) is OOB */ sim_rxeof(m2k, 3000); printf("modest lie 3000 -> 2048 cluster: OOB span=%zu\n", (size_t)3000 - 2048); return 0; } |