/*
 * DF-2587 harness module — deterministically confirms the ng_ether NULL deref.
 *
 * The three if_ethersubr callbacks ng_ether_input/ng_ether_input_orphan/
 * ng_ether_output deref IFP2NG(ifp) as node->private with NO NULL check
 * (sys/netgraph/ether/ng_ether.c:206/222/260), unlike ng_ether_detach which
 * guards `if (node == NULL) return;` (:326). IFP2NG(ifp) is NULL on a live
 * interface only via ng_ether_attach failure (memory pressure) or a detach
 * race (interface destroyed during in-flight traffic) — both hard to hit
 * reliably from userspace (ifconfig destroy returns EBUSY while the tap fd is
 * open, and netisr drains bursts before detach).
 *
 * This module sets up the EXACT post-race / failed-attach state on the live
 * vtnet0 interface — IFP2NG(vtnet0) := NULL — so the very next packet on
 * vtnet0 exercises the unguarded deref. On load it nulls ac_netgraph; the
 * driver then sends any packet (e.g. `ping 10.0.2.2`) -> ether_output ->
 * ng_ether_output(vtnet0) -> node = IFP2NG(vtnet0) = NULL -> node->private
 * -> NULL deref -> panic. This is function-level primitive confirmation
 * (analogous to harness-only findings).
 *
 * On a FIXED ng_ether.ko (NULL check in all three callbacks) the same setup
 * does NOT crash: ng_ether_output sees node==NULL and returns 0; ping works.
 *
 * IFP2NG(ifp) == ((struct arpcom *)ifp)->ac_netgraph  (ng_ether.c:68-69,
 * arpcom has struct ifnet ac_if as its first member, so the cast is valid).
 *
 * Build/run (root): see build_mod.sh / run_mod.sh
 */
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_arp.h>

static struct ifnet *saved_ifp;
static void *saved_ng;

static int
harness_load(module_t mod, int what, void *arg)
{
	struct arpcom *ac;
	struct ifnet *ifp;

	switch (what) {
	case MOD_LOAD:
		ifnet_lock();			/* ifunit() requires ifnet_mtx */
		ifp = ifunit("vtnet0");
		if (ifp == NULL) {
			ifnet_unlock();
			kprintf("DF2587: vtnet0 not found\n");
			return ENXIO;
		}
		ac = (struct arpcom *)ifp;	/* ac_if is first member of arpcom */
		saved_ifp = ifp;
		saved_ng = ac->ac_netgraph;
		ac->ac_netgraph = NULL;		/* IFP2NG(vtnet0) := NULL */
		ifnet_unlock();
		kprintf("DF2587: IFP2NG(vtnet0) set NULL (was %p). "
			"Next packet on vtnet0 -> ng_ether_output/input NULL deref.\n",
			saved_ng);
		return 0;
	case MOD_UNLOAD:
		ifnet_lock();
		if (saved_ifp != NULL) {
			((struct arpcom *)saved_ifp)->ac_netgraph = saved_ng;
			kprintf("DF2587: restored IFP2NG(vtnet0)=%p\n", saved_ng);
		}
		ifnet_unlock();
		return 0;
	}
	return 0;
}

static moduledata_t df2587_harness = {
	"df2587_harness",
	harness_load,
	NULL
};
DECLARE_MODULE(df2587_harness, df2587_harness, SI_SUB_PSEUDO, SI_ORDER_ANY);
