DragonFlyBSD Kernel Audit
DF-0735 / ng_df735_poc.c
← back to finding ↓ download raw
/*
 * ng_df735_poc.c - DF-0735 faithful demonstration harness.
 *
 * This is a minimal netgraph node that reproduces, line-for-line, the
 * wrong-context call in sys/netgraph7/ng_ipfw.c:248 (and :245). The
 * real ng_ipfw.c cannot be compiled on this DragonFly tree because its
 * `#include <netinet/ip_fw.h>` does not resolve (no such header exists
 * under sys/netinet/ -- ipfw.h lives at sys/net/ipfw/ip_fw.h with a
 * different API), and even if it could be compiled, no in-kernel caller
 * ever invokes its `ng_ipfw_input_p` function pointer (it is referenced
 * only inside ng_ipfw.c itself), so the cited rcvdata path is dead code
 * on any running DragonFly kernel.
 *
 * To prove the source-level mechanism is real (calling ip_input from
 * the netgraph worker thread trips the netisr assertion), this harness
 * implements a netgraph node whose rcvdata callback executes the exact
 * body of ng_ipfw_rcvdata() at lines 220-256:
 *
 *    NGI_GET_M(item, m);
 *    NG_FREE_ITEM(item);
 *    ...
 *    ip_input(m);                  <-- ng_ipfw.c:248, the cited bug
 *
 * The hook is created with NG_HOOK_FORCE_QUEUE (mirroring ng_ipfw.c:185
 * ng_ipfw_connect), so the data is queued asynchronously and processed
 * by the netgraph worker thread created at ng_base.c:2787-2789 -- which
 * is NOT on the netreglist (only netmsg_service_port_init ports at
 * netisr.c:258-280 are). Therefore the ASSERT_NETISR_NCPUS(mycpuid) at
 * the top of ip_input (ip_input.c:460) fires on any default-GENERIC
 * kernel (INVARIANTS ON).
 *
 * Trigger (as root):
 *   kldload ng_socket.ko
 *   kldload ./ng_df735_poc.ko
 *   ngctl mkpeer df735_poc: hook1 hook2          # creates a df735_poc node with hook "hook1"
 *   ngctl name df735_poc:hook1 dfpoc             # name the peer for easy access -- skip, simpler:
 *   ngctl msg df735_poc: setver 0                # noop, just to verify the node is alive
 *   # The simplest inject path: open the node's hook via ng_socket and write 28 bytes
 *   # (a minimal IPv4 header) into it; the data is queued, the netgraph worker thread
 *   # calls our rcvdata, which calls ip_input(m) -> panic.
 *
 * See run_df735.sh for the exact ngctl sequence.
 */

#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 <net/if.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>

#include <netgraph7/ng_message.h>
#include <netgraph7/netgraph.h>

/* ip_input() is defined at sys/netinet/ip_input.c:445 but not declared in
 * any public header. ng_ipfw.c gets an implicit declaration for the same
 * call (line 248); we declare it explicitly. */
extern void ip_input(struct mbuf *);

/* Netgraph methods */
static ng_constructor_t	df735_cons;
static ng_newhook_t	df735_newhook;
static ng_connect_t	df735_connect;
static ng_rcvdata_t	df735_rcvdata;
static ng_disconnect_t	df735_disconnect;

/* Netgraph node type */
#define DF735_NODE_TYPE	"df735_poc"
#define DF735_HOOK_NAME	"hook1"

static struct ng_type df735_typestruct = {
	.version =	NG_ABI_VERSION,
	.name =		DF735_NODE_TYPE,
	.constructor =	df735_cons,
	.newhook =	df735_newhook,
	.connect =	df735_connect,
	.rcvdata =	df735_rcvdata,
	.disconnect =	df735_disconnect,
};
NETGRAPH_INIT(df735_poc, &df735_typestruct);

static int
df735_cons(node_p node)
{
	return (0);
}

static int
df735_newhook(node_p node, hook_p hook, const char *name)
{
	if (strcmp(name, DF735_HOOK_NAME) != 0)
		return (EINVAL);
	return (0);
}

/*
 * Mirror ng_ipfw.c:182-187 ng_ipfw_connect: force queueing so the data
 * is processed asynchronously by the netgraph worker thread, NOT inline
 * by the sender. This is what makes the subsequent ip_input call run in
 * the wrong (netgraph) thread context.
 */
static int
df735_connect(hook_p hook)
{
	NG_HOOK_FORCE_QUEUE(hook);
	return (0);
}

/*
 * Faithful copy of ng_ipfw_rcvdata() at sys/netgraph7/ng_ipfw.c:220-256
 * for the NG_IPFW_IN branch (line 248: ip_input(m)).
 *
 * We skip the m_tag NGM_IPFW_COOKIE lookup that ng_ipfw.c:228-232 does,
 * because that tag is only ever set by ng_ipfw_input() (the dead-code
 * ipfw callback at line 259) and is irrelevant to demonstrating the
 * wrong-context call. The KASSERT in ip_input fires before any of the
 * packet-body logic, so the packet contents do not matter either.
 */
static int
df735_rcvdata(hook_p hook, item_p item)
{
	struct mbuf *m;

	NGI_GET_M(item, m);
	NG_FREE_ITEM(item);

	/*
	 * This is the cited bug -- ng_ipfw.c:248. The rcvdata runs in the
	 * netgraph worker thread (ng_base.c:2787-2789), which is NOT a
	 * netisr thread. ip_input.c:460 opens with ASSERT_NETISR_NCPUS ->
	 * panic on default-GENERIC (INVARIANTS ON).
	 */
	ip_input(m);

	return (0);
}

static int
df735_disconnect(hook_p hook)
{
	if (NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0 &&
	    NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))
		ng_rmnode_self(NG_HOOK_NODE(hook));
	return (0);
}