/*
 * dsp_race.c -- DF-4322 evidence: shared `static struct cdev *dev` in
 * dsp_clone() (sys/dev/sound/pcm/dsp.c:2381) is a single global handed
 * across concurrently-running devfs clone handlers.  devfs_clone() drops
 * devfs_lock around the handler (devfs_core.c), so two opens on two
 * different pcm units run dsp_clone() simultaneously; the global `dev`
 * written by one card's handler can be read by the other's, and the wrong
 * card's clone cdev is handed back to devfs for the open.
 *
 * UNPRIVILEGED.  Needs two registered pcm units (here: pcm0=ES1370,
 * pcm1=intel-hda).
 *
 * Method: hammer open("/dev/dsp0") and open("/dev/dsp1") in parallel
 * threads; after each successful open, fstat() the fd and check the
 * unit byte of the minor (PCMUNIT = minor & 0xff).  If an fd obtained
 * via /dev/dspN carries the other unit's minor, the clone handler raced
 * us onto the other card's cdev.  On mismatch, confirm with
 * SNDCTL_AUDIOINFO (dev=-1) which prints the backing channel's devnode.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>

#define	THREADS_PER_CARD	6
#define	RUN_SECONDS		180

static volatile int stop = 0;
static volatile int mismatches = 0;
static unsigned long total_iters = 0, total_ebusy = 0, total_eagain = 0;
static pthread_mutex_t mx = PTHREAD_MUTEX_INITIALIZER;

static void
confirm_and_report(int fd, int opened_unit, int got_unit)
{
	oss_audioinfo ai;

	memset(&ai, 0, sizeof(ai));
	ai.dev = -1;
	if (ioctl(fd, SNDCTL_AUDIOINFO, &ai) == 0)
		printf("[!] RACE HIT: opened /dev/dsp%d, fd is clone of card %d "
		    "(devnode %s, name %s)\n",
		    opened_unit, got_unit, ai.devnode, ai.name);
	else
		printf("[!] RACE HIT: opened /dev/dsp%d, fd minor unit=%d "
		    "(audioinfo failed: %s)\n",
		    opened_unit, got_unit, strerror(errno));
}

static void *
hammer(void *arg)
{
	int unit = (int)(intptr_t)arg;
	char path[32];
	struct stat st;
	int fd, got;
	unsigned long it = 0, eb = 0;

	snprintf(path, sizeof(path), "/dev/dsp%d", unit);

	while (!stop) {
		fd = open(path, O_WRONLY);
		if (fd < 0) {
			if (errno == EBUSY)
				eb++;
			continue;
		}
		it++;
		if (fstat(fd, &st) == 0) {
			got = minor(st.st_rdev) & 0xff;
			if (got != unit) {
				pthread_mutex_lock(&mx);
				mismatches++;
				confirm_and_report(fd, unit, got);
				pthread_mutex_unlock(&mx);
			}
		}
		close(fd);
	}
	pthread_mutex_lock(&mx);
	total_iters += it;
	total_ebusy += eb;
	pthread_mutex_unlock(&mx);
	return (NULL);
}

int
main(void)
{
	pthread_t th[2 * THREADS_PER_CARD];
	int i, n = 0;

	printf("dsp_race: %d threads/card, %d s -- open /dev/dsp0 vs /dev/dsp1\n",
	    THREADS_PER_CARD, RUN_SECONDS);

	for (i = 0; i < THREADS_PER_CARD; i++) {
		pthread_create(&th[n++], NULL, hammer, (void *)(intptr_t)0);
		pthread_create(&th[n++], NULL, hammer, (void *)(intptr_t)1);
	}
	sleep(RUN_SECONDS);
	stop = 1;
	for (i = 0; i < n; i++)
		pthread_join(th[i], NULL);

	printf("dsp_race: %lu open cycles (%lu EBUSY), %d cross-card mismatches\n",
	    total_iters, total_ebusy, mismatches);
	return (mismatches > 0) ? 1 : 0;
}
