# DF-4468 VERDICT — snd_solo dangling /dev/mixerN after failed attach (8th family instance)

## Status: INCONCLUSIVE (code-proven; not exercisable on this guest)

The audit guest has no ESS Solo-1 PCI function (QEMU emulates es1370, not
ES1938/ES1946), and the failure precondition (`pcm_register()` failing after
`mixer_init()` succeeded) requires either a feeder-subsystem veto flag or
> PCMMAXUNIT sound units — neither injectable at runtime on stock hardware.
Same verification posture as family instances DF-4421 (ich) and DF-4457 (cmi).

## Root cause (path:line)

1. `sys/dev/sound/pci/solo.c:1045` — `mixer_init(dev, &solomixer_class, sc)`
   is called BEFORE `pcm_register` (line 1052).
2. `sys/dev/sound/pcm/mixer.c:730-733` — `mixer_init()` immediately does
   `make_dev(&mixer_ops, ..., 0666, "mixer%d", unit)` and stores
   `pdev->si_drv1 = m; snddev->mixer_dev = pdev;` where the kobj mixer `m`
   keeps `m->devinfo = sc` (mixer.c:616-649, `mixer_obj_create`).
3. `sys/dev/sound/pci/solo.c:1052-1053` — if `pcm_register(dev, sc, 1, 1)`
   fails, `goto no` runs `ess_release_resources(sc, dev)`.
4. `sys/dev/sound/pci/solo.c:878` — `kfree(sc, M_DEVBUF)`.  **No
   `mixer_uninit()` runs**: the only caller of `mixer_uninit` is
   `pcm_unregister` (sound.c:1205), which newbus never invokes for a failed
   attach.
5. Result: `/dev/mixerN` remains a registered world-writable cdev whose kobj
   devinfo points into freed memory.  First `open()`+`ioctl()` by any local
   user reaches `essmix_init/essmix_set/essmix_setrecsrc`
   (solo.c:651/668/728) → `mix_getdevinfo(m)` (mixer.c:616) returns the freed
   `sc` → `ess_setmixer()` (solo.c:241) reads `sc->sb` (a `struct resource *`)
   from freed heap and performs port I/O via `rman_get_bustag(sc->sb)`.
   UAF-read → forged bus_space handle → out-of-bounds port I/O; realistic
   outcome is kernel fault / DoS, with heap-grooming dependent potential for
   worse.

## Why pcm_register can fail (solo.c:1052)

- `sound.c:1021-1025` — `pcm_veto_load` set (feeder subsystem error at boot,
  sound.c:50 / feeder.c:126).
- `sound.c:1027-1033` — device unit > PCMMAXUNIT.

Both are operator/boot dependent; not attacker-triggerable on demand — hence
Low severity, consistent with the 7 filed family instances.

## Fix

Match the family fix: unwind the mixer before freeing resources —

```diff
--- a/sys/dev/sound/pci/solo.c
+++ b/sys/dev/sound/pci/solo.c
@@ -1049,6 +1049,8 @@ ess_attach(device_t dev)
 	     	rman_get_start(sc->io), rman_get_start(sc->sb), rman_get_start(sc->vc),
 		rman_get_start(sc->irq),PCM_KLDSTRING(snd_solo));
 
+	sc->mixer_ok = 1;	/* mark: mixer_init() succeeded */
+
     	if (pcm_register(dev, sc, 1, 1))
 		goto no;
@@ -1060,6 +1062,9 @@ ess_attach(device_t dev)
 
 no:
+	if (sc->mixer_ok)
+		mixer_uninit(dev);
     	ess_release_resources(sc, dev);
     	return ENXIO;
 }
```

(plus `int mixer_ok;` in `struct ess_info`).  The systemic fix is moving the
`mixer_init()` call after `pcm_register()`+`pcm_addchan()` success across the
whole sound family, or making `pcm_register` failure paths call
`mixer_uninit`.

## Attempts

- Guest run of `./trigger /dev/mixer0` → ENOENT (no ESS hw): run.log.
- Code-level trace completed as above; no false-positive indicators.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
