/*
 * 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;
}
