⬢ DragonFlyBSD Kernel Audit
DF-4115 / dsp_battery.c
← back to finding ↓ download raw
/*
 * DF-4115/4116 batch — hdac.c pass-2 live robustness battery (UNPRIVILEGED).
 *
 * Hammers the user-reachable paths of sys/dev/sound/pci/hda/hdac.c through
 * the hdaa channel discipline on /dev/dsp:
 *   - hdac_stream_start() BDL walk bounds (blkcnt vs HDA_BDL_MAX=256,
 *     hdaa.c clamp chain) via SNDCTL_DSP_SETFRAGMENT extremes
 *   - hdac_stream_alloc()/free() stream-number + bandwidth math via
 *     format/speed/channel churn incl. invalid values
 *   - hdac_stream_getptr()/stop()/reset() via play/trigger/close churn
 *   - concurrent stream exhaustion (hdac_find_stream ISS/OSS walks)
 *
 * Success criterion: guest stays alive, clamped values reported, no hang.
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <sys/wait.h>

#define CHECK(cond, msg) do { if (!(cond)) { \
	printf("[-] %s (line %d): %s\n", msg, __LINE__, strerror(errno)); \
	fails++; } } while (0)

static int fails = 0, rounds = 0;

static const int frags[] = {
	0x7fff0000,	/* 32767 frags of 1 byte */
	0x00010000 | 0,/* 1 frag of 1 byte */
	0x00020000 | 1,/* 2 frags of 2 bytes */
	0xffff0000 | 0x1f, /* 65535 frags of 128MB */
	0x80000000 | 0x1e, /* "negative" frag count */
	0x00000000,	/* 0/0 */
	0x7fffffff, 0xffffffff, 0x01000100, /* 256 frags x 256B */
	0x00ff0000 | 0x04, /* 255 frags x 16B */
};
#define NFRAGS ((int)(sizeof(frags)/sizeof(frags[0])))

static const int fmts[] = {
	AFMT_U8, AFMT_S8, AFMT_S16_LE, AFMT_S16_BE, AFMT_S32_LE,
	AFMT_S24_LE, 0, 0x7fffffff, 0xffffffff, 0x4000,
};
#define NFMTS ((int)(sizeof(fmts)/sizeof(fmts[0])))

static const int speeds[] = { 1, 2, 4000, 44100, 48000, 96000, 192000,
			      384000, 0x7fffffff, 0, 0x80000000 };
#define NSPEEDS ((int)(sizeof(speeds)/sizeof(speeds[0])))

static const int chans[] = { 1, 2, 3, 4, 6, 8, 12, 0, 0x7fffffff, 0xffffffff };
#define NCHANS ((int)(sizeof(chans)/sizeof(chans[0])))

static void phase_fragments(void)
{
	int fd = open("/dev/dsp", O_WRONLY | O_NONBLOCK);
	if (fd < 0) { printf("[-] open dsp: %s\n", strerror(errno)); fails++; return; }
	for (int i = 0; i < NFRAGS; i++) {
		int arg = frags[i], blk = -1;
		audio_buf_info oi;
		memset(&oi, 0, sizeof(oi));
		errno = 0;
		ioctl(fd, SNDCTL_DSP_RESET, NULL);
		(void)ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &arg);
		if (ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &blk) == 0 && blk <= 0) {
			printf("[-] frag %#x: GETBLKSIZE=%d\n", frags[i], blk);
			fails++;
		}
		if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &oi) == 0 &&
		    (oi.fragstotal < 0 || oi.fragstotal > 4096 ||
		     oi.fragsize < 0 || oi.bytes < 0)) {
			printf("[-] frag %#x: bogus ospace %d/%d/%d\n",
			    frags[i], oi.fragsize, oi.fragstotal, oi.bytes);
			fails++;
		}
		rounds++;
	}
	close(fd);
	printf("[+] phase fragments: %d rounds ok (clamp chain held)\n", NFRAGS);
}

static void phase_format_churn(void)
{
	int fd = open("/dev/dsp", O_WRONLY | O_NONBLOCK);
	if (fd < 0) { printf("[-] open dsp: %s\n", strerror(errno)); fails++; return; }
	for (int f = 0; f < NFMTS; f++)
		for (int s = 0; s < NSPEEDS; s++)
			for (int c = 0; c < NCHANS; c++) {
				int fmt = fmts[f], sp = speeds[s], ch = chans[c];
				char junk[512];
				ioctl(fd, SNDCTL_DSP_RESET, NULL);
				(void)ioctl(fd, SNDCTL_DSP_SETFMT, &fmt);
				(void)ioctl(fd, SNDCTL_DSP_SPEED, &sp);
				(void)ioctl(fd, SNDCTL_DSP_CHANNELS, &ch);
				/* actually start the stream: this runs
				 * hdac_stream_start() + BDL fill */
				(void)write(fd, junk, sizeof(junk));
				rounds++;
			}
	close(fd);
	printf("[+] phase format churn: %d combos ok\n", NFMTS * NSPEEDS * NCHANS);
}

static void phase_play_churn(void)
{
	/* tight open/config/play/close loop: exercises hdac_stream_alloc,
	 * start, getptr (feed), stop, reset, free repeatedly */
	char buf[16384];
	memset(buf, 0x55, sizeof(buf));
	for (int i = 0; i < 200; i++) {
		int fd = open("/dev/dsp", O_WRONLY);
		if (fd < 0) continue;
		int fmt = AFMT_S16_LE, ch = 2, sp = 48000, frag = 0x0100000c;
		(void)ioctl(fd, SNDCTL_DSP_SETFMT, &fmt);
		(void)ioctl(fd, SNDCTL_DSP_CHANNELS, &ch);
		(void)ioctl(fd, SNDCTL_DSP_SPEED, &sp);
		(void)ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag);
		(void)write(fd, buf, sizeof(buf));
		(void)ioctl(fd, SNDCTL_DSP_SYNC, NULL);
		close(fd);
		rounds++;
	}
	printf("[+] phase play churn: 200 open/play/close ok\n");
}

static void worker(int id)
{
	int fd;
	char buf[4096];
	memset(buf, id, sizeof(buf));
	for (int i = 0; i < 120; i++) {
		fd = open(id & 1 ? "/dev/dsp" : "/dev/dsp0",
		    (id & 2 ? O_RDWR : O_WRONLY) | O_NONBLOCK);
		if (fd < 0) continue;
		int fmt = AFMT_S16_LE, sp = (i % 3) ? 44100 : 192000, ch = 2;
		(void)ioctl(fd, SNDCTL_DSP_SETFMT, &fmt);
		(void)ioctl(fd, SNDCTL_DSP_SPEED, &sp);
		(void)ioctl(fd, SNDCTL_DSP_CHANNELS, &ch);
		(void)write(fd, buf, sizeof(buf));
		usleep(2000 + id * 300);
		close(fd);
	}
	_exit(0);
}

static void phase_concurrent(void)
{
	pid_t pids[6];
	int st;
	for (int i = 0; i < 6; i++) {
		pids[i] = fork();
		if (pids[i] == 0) worker(i);
	}
	for (int i = 0; i < 6; i++) waitpid(pids[i], &st, 0);
	printf("[+] phase concurrent (6 workers x open/play/close churn) ok\n");
}

int main(void)
{
	setvbuf(stdout, NULL, _IONBF, 0);
	alarm(240);			/* hard watchdog: never hang the guest */
	printf("[*] hdac.c pass-2 ioctl battery start (unprivileged)\n");
	phase_fragments();
	phase_format_churn();
	phase_play_churn();
	phase_concurrent();
	printf("[+] battery COMPLETE: %d rounds, %d failures, guest alive\n",
	    rounds, fails);
	return fails ? 1 : 0;
}