⬢ DragonFlyBSD Kernel Audit
DF-2574 / df_ngdpoc.c
← back to finding ↓ download raw
/*
 * df_ngdpoc.c -- DF-2573 / DF-2574 driver harness (guest-only scaffolding).
 *
 * Compiled INTO the X86_64_NGDPOC kernel (runtime kldload GPFs on this
 * guest).  It drives the REAL compiled-in netgraph machinery (ng_base.c:
 * ng_mkpeer -> ng_add_hook -> type->newhook, ng_destroy_hook ->
 * ng_disconnect_hook -> type->disconnect) against the REAL "device" node
 * created at boot by the verbatim ng_device.c code under test
 * (netgraph/ng_device_poc.c = shim prologue + byte-verbatim ng_device.c +
 * accessor epilogue; sha256 attested at boot and by kern.ngdpoc.attest).
 *
 * Fault injection knobs:
 *   kern.ngdpoc.fail_makedev   -- fail the next N make_dev() calls made by
 *                                 the code under test (DF-2573, scoped to
 *                                 the ngdpoc shim only)
 *   trigger "mkpeer_failreadq" -- arms a one-shot kmalloc(10240, M_DEVBUF)
 *                                 failure (patched into kern_slaballoc.c)
 *                                 and immediately drives newhook (DF-2573)
 *
 * Leak instrumentation (DF-2574): kern.ngdpoc.stats reports the exact
 * in-kernel M_DEVBUF malloc-type accounting (memuse/inuse/calls summed over
 * all CPUs) plus the live ngd connection list.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/thread2.h>
#include <sys/globaldata.h>

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

/* knobs consumed by ngdpoc shim + kern_slaballoc.c interposer */
int ngdpoc_fail_makedev_nth = 0;
int ngdpoc_kmalloc_fail_armed = 0;
unsigned long ngdpoc_kmalloc_fail_size = 0;

/* accessors exported by the poc TU */
size_t ngdpoc_conn_size(void);
int ngdpoc_list_count(void);
void ngdpoc_list_dump(char *buf, size_t len);

static void
ngdpoc_mdevbuf_stats(unsigned long *memuse, unsigned long *inuse,
		     unsigned long *calls)
{
	struct malloc_type *t = M_DEVBUF;
	unsigned long m = 0, i = 0, c = 0;
	int n;

	crit_enter();
	for (n = 0; n < ncpus; ++n) {
		m += t->ks_use[n].memuse;
		i += t->ks_use[n].inuse;
		c += t->ks_use[n].calls;
	}
	crit_exit();
	*memuse = m;
	*inuse = i;
	*calls = c;
}

static node_p
ngdpoc_devnode(void)
{
	node_p node = ng_findname(NULL, "device");

	if (node == NULL)
		kprintf("NGDPOC: ERROR: netgraph 'device' node not found\n");
	return (node);
}

/* ---- optional fallback peer type (used only if ng_echo is absent) ---- */

static int ngdpoc_peer_cons(node_p *nodep) { return (0); }

static char ngdpoc_tn_echo[] = "echo";
static char ngdpoc_tn_dfpeer[] = "dfpeer";

static struct ng_type ngdpoc_peer_type = {
	NG_VERSION,
	"dfpeer",
	NULL,
	ngdpoc_peer_cons,
	NULL,			/* rcvmsg */
	NULL,			/* shutdown */
	NULL,			/* newhook */
	NULL,			/* findhook */
	NULL,			/* connect */
	NULL,			/* rcvdata */
	NULL,			/* rcvdataq */
	NULL,			/* disconnect */
	NULL			/* cmdlist */
};

static int
ngdpoc_ensure_peer_type(void)
{
	if (ng_findtype("echo") != NULL)
		return (0);
	if (ng_findtype("dfpeer") != NULL)
		return (0);
	return (ng_newtype(&ngdpoc_peer_type));
}

static char *
ngdpoc_peer_type_arg(void)
{
	if (ng_findtype("echo") != NULL)
		return (ngdpoc_tn_echo);
	return (ngdpoc_tn_dfpeer);
}

/*
 * Do the real work of one connect cycle through the REAL netgraph paths:
 * ng_mkpeer() is exactly what ngctl mkpeer ends up calling (ng_base.c:1270),
 * ng_destroy_hook() exactly what ngctl rmhook/shutdown ends up calling
 * (ng_base.c:1314-1315).
 */
static int
ngdpoc_one_cycle(node_p node, const char *hookname)
{
	hook_p hook;
	int error;

	error = ng_mkpeer(node, hookname, "peer", ngdpoc_peer_type_arg());
	if (error) {
		kprintf("NGDPOC: cycle %s: ng_mkpeer error %d\n", hookname, error);
		return (error);
	}
	hook = ng_findhook(node, hookname);
	if (hook == NULL) {
		kprintf("NGDPOC: cycle %s: hook vanished after mkpeer\n", hookname);
		return (ENOENT);
	}
	ng_destroy_hook(hook);		/* -> ng_device_disconnect() */
	return (0);
}

static char ngdpoc_lastcmd[128];

static int
ngdpoc_do_cmd(char *cmd)
{
	char *p = cmd, *arg, *arg2;
	node_p node;
	int n, error, i;
	unsigned long m0, i0, c0;
	char buf[512];

	arg = strsep(&p, " ");
	if (arg == NULL || *arg == '\0')
		return (EINVAL);

	if (strcmp(arg, "state") == 0) {
		ngdpoc_list_dump(buf, sizeof(buf));
		kprintf("NGDPOC: %s\n", buf);
		return (0);
	}
	if (strcmp(arg, "regpeer") == 0)
		return (ngdpoc_ensure_peer_type());

	if (strcmp(arg, "cycle") == 0) {
		arg = strsep(&p, " ");
		if (arg == NULL || *arg == '\0')
			return (EINVAL);
		n = strtol(arg, NULL, 0);
		node = ngdpoc_devnode();
		if (node == NULL)
			return (ENOENT);
		ngdpoc_mdevbuf_stats(&m0, &i0, &c0);
		kprintf("NGDPOC: cycle %d BEGIN  M_DEVBUF memuse=%lu inuse=%lu calls=%lu list=%d\n",
		    n, m0, i0, c0, ngdpoc_list_count());
		for (i = 0; i < n; ++i) {
			ksnprintf(buf, sizeof(buf), "c%d", i);
			if ((error = ngdpoc_one_cycle(node, buf)) != 0)
				return (error);
		}
		ngdpoc_mdevbuf_stats(&m0, &i0, &c0);
		kprintf("NGDPOC: cycle %d END    M_DEVBUF memuse=%lu inuse=%lu calls=%lu list=%d\n",
		    n, m0, i0, c0, ngdpoc_list_count());
		return (0);
	}

	if (strcmp(arg, "mkpeer") == 0 || strcmp(arg, "mkpeer_failreadq") == 0) {
		int failreadq = (strcmp(arg, "mkpeer_failreadq") == 0);

		arg = strsep(&p, " ");
		arg2 = strsep(&p, " ");		/* optional explicit type */
		if (arg == NULL || *arg == '\0')
			return (EINVAL);
		node = ngdpoc_devnode();
		if (node == NULL)
			return (ENOENT);
		if (arg2 == NULL)
			arg2 = ngdpoc_peer_type_arg();
		if (strcmp(arg2, "dfpeer") == 0)
			ngdpoc_ensure_peer_type();
		if (failreadq) {
			/* DF-2573: one-shot kmalloc(10240, M_DEVBUF) failure,
			 * armed and consumed inside this same syscall */
			ngdpoc_kmalloc_fail_size = 10240;	/* NGD_QUEUE_SIZE */
			ngdpoc_kmalloc_fail_armed = 1;
			kprintf("NGDPOC: armed one-shot kmalloc(%lu, M_DEVBUF) failure\n",
			    ngdpoc_kmalloc_fail_size);
		}
		error = ng_mkpeer(node, arg, "peer", arg2);
		kprintf("NGDPOC: ng_mkpeer(%s, type %s) = %d\n", arg, arg2, error);
		ngdpoc_list_dump(buf, sizeof(buf));
		kprintf("NGDPOC: %s\n", buf);
		return (error);
	}

	if (strcmp(arg, "rmhook") == 0) {
		arg = strsep(&p, " ");
		if (arg == NULL || *arg == '\0')
			return (EINVAL);
		node = ngdpoc_devnode();
		if (node == NULL)
			return (ENOENT);
		{
			hook_p hook = ng_findhook(node, arg);

			if (hook == NULL) {
				kprintf("NGDPOC: rmhook: no hook '%s'\n", arg);
				return (ENOENT);
			}
			ng_destroy_hook(hook);	/* -> ng_device_disconnect() */
		}
		ngdpoc_list_dump(buf, sizeof(buf));
		kprintf("NGDPOC: %s\n", buf);
		return (0);
	}

	kprintf("NGDPOC: unknown command '%s'\n", arg);
	return (EINVAL);
}

static int
ngdpoc_trigger_sysctl(SYSCTL_HANDLER_ARGS)
{
	char buf[128];
	int error;

	bzero(buf, sizeof(buf));
	strlcpy(buf, ngdpoc_lastcmd, sizeof(buf));
	error = sysctl_handle_string(oidp, buf, sizeof(buf), req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	strlcpy(ngdpoc_lastcmd, buf, sizeof(ngdpoc_lastcmd));
	/*
	 * Always return 0: the kernel sysctl machinery retries a string
	 * write whose handler returns an error, which would re-execute the
	 * command (observed: infinite retry loop).  Command results are
	 * reported via kprintf/dmesg.
	 */
	ngdpoc_do_cmd(buf);
	return (0);
}

static int
ngdpoc_stats_sysctl(SYSCTL_HANDLER_ARGS)
{
	char buf[512];
	unsigned long m, i, c;

	ngdpoc_mdevbuf_stats(&m, &i, &c);
	ngdpoc_list_dump(buf, sizeof(buf));
	ksnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
	    " | M_DEVBUF memuse=%lu inuse=%lu calls=%lu",
	    m, i, c);
	return (sysctl_handle_string(oidp, buf, sizeof(buf), req));
}

SYSCTL_NODE(_kern, OID_AUTO, ngdpoc, CTLFLAG_RW, 0, "NGDPOC verification harness");
SYSCTL_PROC(_kern_ngdpoc, OID_AUTO, trigger, CTLTYPE_STRING | CTLFLAG_RW,
	    0, 0, ngdpoc_trigger_sysctl, "A",
	    "state | cycle N | mkpeer H [type] | mkpeer_failreadq H | rmhook H");
SYSCTL_PROC(_kern_ngdpoc, OID_AUTO, stats, CTLTYPE_STRING | CTLFLAG_RD,
	    0, 0, ngdpoc_stats_sysctl, "A",
	    "M_DEVBUF accounting + ngd connection list");
SYSCTL_INT(_kern_ngdpoc, OID_AUTO, fail_makedev, CTLFLAG_RW,
	    &ngdpoc_fail_makedev_nth, 0,
	    "DF-2573: fail the next N make_dev() calls in the code under test");