/*
 * DF-4432 replica — proves the sink of the if_sln.c NULL-mbuf RX bug.
 *
 * Bug chain (all path:line against this repo's sys/ tree):
 *   1. if_sln.c:913 / if_sln.c:926   m = m_devget(rx_bufpos - 2, pkt_size + 2, 0, ifp)
 *   2. sys/kern/uipc_mbuf.c:2246-2249  m_getl(M_NOWAIT) failure => m_devget returns NULL
 *   3. if_sln.c:915-919 / 928-937     if (m == NULL) { ierrors++; ... }   -- and falls through
 *   4. if_sln.c:949                   ifp->if_input(ifp, m, NULL, -1);  -- UNCONDITIONAL, m == NULL
 *   5. sys/net/if_ethersubr.c:1494    M_ASSERTPKTHDR(m)  (KASSERT only under INVARIANTS)
 *   6. sys/net/if_ethersubr.c:1502    m->m_len            (NULL deref on GENERIC -> fatal trap)
 *
 * The ancestor driver rl(4) guards this exact pattern with
 *   if_rl.c:1095-1096   if (m == NULL) continue;
 * before if_rl.c:1099 ifp->if_input(ifp, m, NULL, -1);
 * The sln(4) port dropped that guard.
 *
 * This module creates a transient IFF_UP ethernet interface and issues the
 * EXACT statement of if_sln.c:949 with the EXACT faulting argument
 * (m == NULL, the m_devget-failure value), from kldload context.
 * Expected result on X86_64_GENERIC: Fatal trap 12 page fault, vaddr ~0x18
 * (offsetof(struct mbuf, m_len)), i.e. the identical panic a real sln(4)
 * RX interrupt would take when m_devget() fails under cluster exhaustion.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>

#include <net/if.h>
#include <net/if_var.h>
#include <net/if_types.h>
#include <net/if_arp.h>
#include <net/ethernet.h>

static struct ifnet *dfrn_ifp;

static int
dfrn_modevent(module_t mod, int type, void *arg)
{
	static const uint8_t lla[ETHER_ADDR_LEN] =
	    { 0x02, 0xdf, 0x44, 0x32, 0x00, 0x01 };
	struct mbuf *m = NULL;		/* m_devget() failure value, if_sln.c:915/928 */

	switch (type) {
	case MOD_LOAD:
		dfrn_ifp = if_alloc(IFT_ETHER);
		if (dfrn_ifp == NULL)
			return (ENOMEM);
		if_initname(dfrn_ifp, "dfrn", 0);
		dfrn_ifp->if_mtu = ETHERMTU;
		/* sln_rx() only ever runs with the interface UP+RUNNING
		 * (if_sln.c:1075 gates the interrupt on IFF_RUNNING;
		 *  if_sln.c:721 sets it in sln_init), so replicate IFF_UP. */
		dfrn_ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX |
				     IFF_MULTICAST | IFF_UP;
		ether_ifattach(dfrn_ifp, lla, NULL);

		kprintf("DF-4432 replica: if_sln.c:949 -> "
		    "if_input(ifp, NULL, NULL, -1) on dfrn0 (UP)\n");
		ifnet_serialize_all(dfrn_ifp);
		/* ---------------- EXACT replica of if_sln.c:949 ---------------- */
		dfrn_ifp->if_input(dfrn_ifp, m, NULL, -1);
		/* ----------------------------------------------------------------- */
		ifnet_deserialize_all(dfrn_ifp);
		kprintf("DF-4432 replica: SURVIVED -- unexpected, bug refuted\n");

		ether_ifdetach(dfrn_ifp);
		if_free(dfrn_ifp);
		return (0);
	case MOD_UNLOAD:
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t dfrn_mod = { "dfrn", dfrn_modevent, NULL };
DECLARE_MODULE(dfrn, dfrn_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_VERSION(dfrn, 1);
