/*
 * DF-2760: sys_getsid (kern_prot.c:153-154) reads pt->p_pgrp->pg_session
 * with NO p_token; sys_setpgid (:383/:404) reads curp->p_session unlocked.
 * Churners continuously setpgid/setsid (pgrp/session alloc/free churn);
 * a checker hammers getsid() on them and validates every returned sid
 * against the registry: {parent sid} U {every churner pid (historical
 * session leaders)}.  Anything else = read through freed memory.
 */
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>

#define NCHURN 24

struct shm_s {
	pid_t churn[NCHURN];
	volatile int cur_sid[NCHURN];	/* churner's current sid */
};
static struct shm_s *shm;
static volatile sig_atomic_t stop;

static void
churner(int idx)
{
	int r;
	shm->churn[idx] = getpid();
	r = setsid();		/* sid = my pid */
	shm->cur_sid[idx] = r < 0 ? getsid(0) : (int)getpid();
	for (;;) {
		setpgid(0, 0);
		if ((idx & 3) == 0 && getsid(0) != getpid())
			shm->cur_sid[idx] = setsid() < 0 ? getsid(0)
							 : (int)getpid();
		usleep(50 + (idx & 15));
	}
	_exit(0);
}

static int
valid_sid(int sid, int parentsid)
{
	int i;
	if (sid == parentsid)
		return 1;
	if (sid <= 1)
		return 0;
	for (i = 0; i < NCHURN; i++)
		if (shm->churn[i] == sid)	/* historical leader pid */
			return 1;
	return 0;
}

int
main(int argc, char **argv)
{
	int secs = argc > 1 ? atoi(argv[1]) : 60;
	int i;
	shm = mmap(NULL, sizeof(*shm), PROT_READ | PROT_WRITE,
	    MAP_ANON | MAP_SHARED, -1, 0);
	memset((void *)shm, 0, sizeof(*shm));

	int parentsid = getsid(0);
	for (i = 0; i < NCHURN; i++) {
		pid_t p = fork();
		if (p == 0)
			churner(i);
	}
	usleep(200000);	/* let churners register */

	long iters = 0, impossible = 0, stale = 0;
	time_t t0 = time(NULL);
	while (time(NULL) - t0 < secs) {
		for (i = 0; i < NCHURN; i++) {
			int sid = getsid(shm->churn[i]);
			iters++;
			if (sid < 0)
				continue;
			if (!valid_sid(sid, parentsid)) {
				impossible++;
				if (impossible <= 10)
					fprintf(stderr,
					    "[!!] getsid(%d)=%d: value never "
					    "was a session id (garbage read "
					    "through freed pgrp/session)\n",
					    shm->churn[i], sid);
			} else if (sid != shm->cur_sid[i] &&
			    sid != shm->churn[i] && sid != parentsid) {
				stale++;	/* benign transition staleness */
			}
		}
	}
	fprintf(stderr, "[*] getsid=%ld impossible=%ld stale-transition=%ld "
	    "parentsid=%d\n", iters, impossible, stale, parentsid);
	fprintf(stderr, "[*] if impossible>0: race reproduced; else window "
	    "not observed this run. Check dmesg for panics.\n");
	kill(0, SIGKILL);
	return 0;
}
