# DF-4610 — neomagic attach-failure paths after mixer_init() leave a live 0666 /dev/mixerN over freed codec+softc (DF-4156 family, 13th instance)

File: `sys/dev/sound/pci/neomagic.c` — pass 2 (GLM 5.3), 2026-09-22.

## THE BUG

`nm_pci_attach()` (neomagic.c:663) publishes the mixer before anything that
can still fail:

```c
693:	codec = AC97_CREATE(dev, sc, nm_ac97);
694:	if (codec == NULL) goto bad;
695:	if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto bad;   <-- 0666 /dev/mixerN goes live here
697:	sc->irqid = 0;
698:	sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE);
700:	if (!sc->irq || snd_setup_intr(dev, sc->irq, 0, nm_intr, sc, &sc->ih)) {
702:		goto bad;                                                        <-- failure window #1
703:	}
...
709:	if (pcm_register(dev, sc, 1, 1)) goto bad;                          <-- failure window #2
```

`mixer_init()` (mixer.c:687-736) ends in
`pdev = make_dev(&mixer_ops, ..., 0666, "mixer%d")` + `pdev->si_drv1 = m`
(mixer.c:730-733) with `m->devinfo = sc` (the neomagic softc, via
AC97_CREATE → ac97_create, ac97.c).  The failure unwind then runs:

```c
716: bad:
717:	if (codec) ac97_destroy(codec);        <-- kobj_delete(methods) + kfree(codec)  (ac97.c:852-858)
722:	kfree(sc, M_DEVBUF);                   <-- softc freed too
```

…without destroying the cdev or the snd_mixer.  Every later open/ioctl on
the surviving world-writable `/dev/mixerN` dispatches through `mixer_ops`
into the freed `snd_mixer` → freed `ac97_info` (its methods kobj was
deleted) → freed `sc` (e.g. `nm_rdcd/nm_wrcd` cast `devinfo` back to
`struct sc_info *` and touch `sc->reg`, a released bus resource) —
use-after-free with function-pointer-bearing kobj dispatch, exactly the
DF-4156/4193/4299/4334/4344/4421/4457/4468/4504/4537/4550/4552 family.

Secondary unwind defect fixed in the same diff: `bad:` releases the two
MEMORY resources (718-719) *before* `bus_teardown_intr` (720), so the
handler could touch unmapped MMIO in between; the fix restores the
canonical order (teardown irq, release irq, release mem).

## THREAT

Unprivileged local user, *provided* the driver attached partially and then
failed: the surviving `/dev/mixerN` is 0666 and its ioctls
(OSS mixer_{g,s}et, MIXER_READ/WRITE, MIXERINFO, REINIT via resume…)
immediately hit freed heap.  Realistic failure triggers are
`bus_alloc_resource_any(SYS_RES_IRQ)` failure (no free IRQ line /
shareable-IRQ exhaustion) and `pcm_register()` failure
(pcm_veto_load stuck, unit > PCMMAXUNIT — sound.c:1021/1027).
Hardware-gated: requires an actual NM256AV (10c8:8005) / NM256ZX
(10c8:8006) audio function.

## VERIFICATION STATUS: untested (hardware-gated)

The audit guest runs QEMU 7.2.22, whose only audio device models are
AC97 / ES1370 / intel-hda / sb16 — there is **no NM256 emulation**, so no
`snd_neomagic` attach can occur there at all (env.txt: full `pciconf -l`,
only `hdac0` 8086:2668 present; run.log: probe output).  The code chain
itself is fully source-proven above (confidence: certain).

* probe: `neomagic_probe.c` — prints the trigger preconditions and the
  guest hardware absence (build/run in run.log).
* fix build validation: `fix.diff` applied to the guest's `/usr/src`
  and `snd_neomagic.ko` rebuilt with `-Werror` — clean
  (build.log; canonical `make` in sys/dev/sound/driver/neomagic).

## FIX

`fix.diff` (validated to apply+compile): move IRQ setup and pcm_register()
*before* codec creation/mixer_init, destroy the codec inline when
mixer_init() fails (nothing published yet in that case), leaving **no
failure path after the mixer is published** (pcm_addchan/pcm_setstatus
cannot fail); also fixes the bad:-unwind resource order.

## 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.
