⬢ DragonFlyBSD Kernel Audit
DF-4293 / mixbattery.c
← back to finding ↓ download raw
/*
 * DF-4293 battery: hostile OSS mixer ioctls against /dev/mixer0 (0666)
 * and /dev/dsp0 'M'-group ioctls, run as an UNPRIVILEGED user.
 *
 * Targets sys/dev/sound/pcm/mixer.c:
 *   - mixer_ioctl_cmd dispatch: j = cmd & 0xff for ALL 256 values,
 *     read+write, with hostile payloads.
 *   - SNDCTL_DSP_{GET,SET}_RECSRC / GET_RECSRC_NAMES with hostile ints.
 *   - SNDCTL_MIXERINFO / AUDIOINFO / ENGINEINFO / SYSINFO / CARDINFO with
 *     hostile dev indices (incl. negative).
 *   - SOUND_MIXER_INFO, OSS_GETVERSION.
 *   - concurrent hammer on set-volume/recsrc/mixerinfo to shake lock races.
 *
 * Success criterion for NEGATIVE result: every call returns cleanly
 * (0/ENXIO/EINVAL/EBADF...), guest stays up, no panic.  Any Fatal trap
 * = new finding.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>

#define MIXER_WRITE_J(j) (0x80044d00u | (j))
#define MIXER_READ_J(j)  (0xc0044d00u | (j))

static int mixfd = -1, dspfd = -1;
static unsigned long n_ok, n_enxio, n_einval, n_other, n_err[16];

static void
tally(const char *what, unsigned long cmd, int rc, int err, int val)
{
	if (rc == 0) { n_ok++; return; }
	if (err == ENXIO) n_enxio++;
	else if (err == EINVAL) n_einval++;
	else { n_other++; if (err > 0 && err < 16) n_err[err]++; }
	(void)what; (void)cmd; (void)val;
}

static void
sweep_all_j(void)
{
	static const int payload[] = {0, 0x6464, 0x7f7f, 0xffffffff,
	    0x0064ffff, (int)0x80000000, 0x7fffffff, 0x0000c8c8};
	unsigned j;
	int v, rc, i;

	for (j = 0; j < 256; j++) {
		/* read direction */
		v = 0xdeadbeef;
		rc = ioctl(mixfd, MIXER_READ_J(j), &v);
		tally("read", MIXER_READ_J(j), rc, errno, v);
		/* write direction, several payloads */
		for (i = 0; i < (int)(sizeof(payload)/sizeof(payload[0])); i++) {
			v = payload[i];
			rc = ioctl(mixfd, MIXER_WRITE_J(j), &v);
			tally("write", MIXER_WRITE_J(j), rc, errno, v);
		}
		/* same read on the dsp device ('M' group goes to the mixer) */
		if (dspfd >= 0) {
			v = 0xcafebabe;
			rc = ioctl(dspfd, MIXER_READ_J(j), &v);
			tally("dspread", MIXER_READ_J(j), rc, errno, v);
			v = payload[3];
			rc = ioctl(dspfd, MIXER_WRITE_J(j), &v);
			tally("dspwrite", MIXER_WRITE_J(j), rc, errno, v);
		}
	}
}

static void
recsrc_battery(void)
{
	static const int vals[] = {0, 1, -1, 2, 100000, 0x7fffffff,
	    (int)0x80000000, 0x40000000, 24, 25, 26, 255, 256};
	unsigned i;
	int v, rc;
	oss_mixer_enuminfo ei;

	for (i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) {
		v = vals[i];
		rc = ioctl(mixfd, SNDCTL_DSP_SET_RECSRC, &v);
		tally("setrecsrc", SNDCTL_DSP_SET_RECSRC, rc, errno, v);
		v = 0x11111111;
		rc = ioctl(mixfd, SNDCTL_DSP_GET_RECSRC, &v);
		tally("getrecsrc", SNDCTL_DSP_GET_RECSRC, rc, errno, v);
		/* also via the legacy RECSRC byte on both directions */
		v = vals[i];
		rc = ioctl(mixfd, MIXER_WRITE_J(0xff), &v);	/* SOUND_MIXER_WRITE_RECSRC */
		tally("wrecsrc", MIXER_WRITE_J(0xff), rc, errno, v);
		v = 0x22222222;
		rc = ioctl(mixfd, MIXER_READ_J(0xff), &v);	/* SOUND_MIXER_READ_RECSRC */
		tally("rrecsrc", MIXER_READ_J(0xff), rc, errno, v);
	}
	memset(&ei, 0xa5, sizeof(ei));
	rc = ioctl(mixfd, SNDCTL_DSP_GET_RECSRC_NAMES, &ei);
	tally("recsrcnames", SNDCTL_DSP_GET_RECSRC_NAMES, rc, errno, 0);
	printf("  RECSRC_NAMES rc=%d nvalues=%d strindex[0]=%d strings0='%.8s'\n",
	    rc, ei.nvalues, ei.strindex[0], ei.strings);
}

static void
xgroup_battery(void)
{
	static const int devs[] = {-1, 0, 1, 2, 100, 0x7fffffff, -2, -100, 3};
	oss_mixerinfo mi;
	oss_audioinfo ai;
	oss_card_info ci;
	oss_sysinfo si;
	unsigned i;
	int rc, v;

	for (i = 0; i < sizeof(devs)/sizeof(devs[0]); i++) {
		memset(&mi, 0xb7, sizeof(mi));
		mi.dev = devs[i];
		rc = ioctl(mixfd, SNDCTL_MIXERINFO, &mi);
		tally("mixerinfo", SNDCTL_MIXERINFO, rc, errno, mi.dev);
		printf("  MIXERINFO dev=%d rc=%d errno=%d name='%.16s'\n",
		    devs[i], rc, errno, rc == 0 ? mi.name : "");
	}
	memset(&ai, 0xc3, sizeof(ai));
	ai.dev = 0;
	rc = ioctl(mixfd, SNDCTL_AUDIOINFO, &ai);
	tally("audioinfo", SNDCTL_AUDIOINFO, rc, errno, ai.dev);
	printf("  AUDIOINFO(mixer dev) rc=%d errno=%d\n", rc, errno);
	memset(&ai, 0xc3, sizeof(ai));
	ai.dev = 0x7fffffff;
	rc = ioctl(mixfd, SNDCTL_ENGINEINFO, &ai);
	tally("engineinfo", SNDCTL_ENGINEINFO, rc, errno, ai.dev);
	printf("  ENGINEINFO dev=max rc=%d errno=%d\n", rc, errno);
	rc = ioctl(mixfd, SNDCTL_SYSINFO, &si);
	tally("sysinfo", SNDCTL_SYSINFO, rc, errno, 0);
	printf("  SYSINFO rc=%d errno=%d nummixers=%d\n", rc, errno, si.nummixers);
	memset(&ci, 0, sizeof(ci));
	ci.card = -5;
	rc = ioctl(mixfd, SNDCTL_CARDINFO, &ci);
	tally("cardinfo", SNDCTL_CARDINFO, rc, errno, ci.card);
	printf("  CARDINFO card=-5 rc=%d errno=%d\n", rc, errno);
	v = 0;
	rc = ioctl(mixfd, OSS_GETVERSION, &v);
	tally("ossver", OSS_GETVERSION, rc, errno, v);
	printf("  OSS_GETVERSION rc=%d ver=%08x\n", rc, v);
	rc = ioctl(mixfd, SOUND_MIXER_INFO, &mi);
	tally("mixer_info", SOUND_MIXER_INFO, rc, errno, 0);
	printf("  SOUND_MIXER_INFO rc=%d errno=%d name='%.16s'\n", rc,
	    errno, rc == 0 ? mi.name : "");
}

static void *
hammer(void *arg)
{
	long id = (long)arg;
	int i, v;
	oss_mixerinfo mi;

	for (i = 0; i < 60000; i++) {
		v = (i & 0x7f) | (((i * 7) & 0x7f) << 8);
		ioctl(mixfd, MIXER_WRITE_J(SOUND_MIXER_VOLUME), &v);
		ioctl(mixfd, MIXER_WRITE_J(0xff), &v);	/* RECSRC flip */
		v = 0;
		ioctl(mixfd, MIXER_READ_J(SOUND_MIXER_VOLUME), &v);
		ioctl(mixfd, MIXER_READ_J(0xfe), &v);	/* DEVMASK */
		if ((i & 0x3ff) == 0) {
			memset(&mi, 0, sizeof(mi));
			mi.dev = -1;
			ioctl(mixfd, SNDCTL_MIXERINFO, &mi);
		}
		if ((i & 0xffff) == id * 1000)
			printf("  hammer %ld at %d\n", id, i);
	}
	return (NULL);
}

int
main(void)
{
	pthread_t th[3];
	long i;

	mixfd = open("/dev/mixer0", O_RDWR);
	if (mixfd < 0) { perror("open /dev/mixer0"); exit(1); }
	dspfd = open("/dev/dsp0", O_WRONLY);	/* may block? use nonblock */
	if (dspfd < 0)
		dspfd = open("/dev/dsp0", O_WRONLY|O_NONBLOCK);
	printf("mixfd=%d dspfd=%d uid=%d\n", mixfd, dspfd, getuid());

	printf("== sweep all 256 j values, read+write+dsproute\n");
	sweep_all_j();
	printf("== recsrc battery\n");
	recsrc_battery();
	printf("== 'X' group battery\n");
	xgroup_battery();
	printf("== concurrent hammer (3 x 60k)\n");
	for (i = 0; i < 3; i++)
		pthread_create(&th[i], NULL, hammer, (void *)i);
	for (i = 0; i < 3; i++)
		pthread_join(th[i], NULL);

	printf("== tally: ok=%lu enxio=%lu einval=%lu other=%lu\n",
	    n_ok, n_enxio, n_einval, n_other);
	for (i = 0; i < 16; i++)
		if (n_err[i])
			printf("   errno %ld: %lu times\n", i, n_err[i]);
	printf("BATTERY COMPLETE, guest still alive: ");
	fflush(stdout);
	printf("%s\n", system("echo yes") == 0 ? "yes" : "NO");
	return (0);
}