nm_pci_attach failure paths after mixer_init() leave a live 0666 /dev/mixerN over freed codec+softc β mixer-UAF family, 13th instance (DF-4156/4193/4299/4334/4344/4421/4457/4468/4504/4537/4550)
Summary
mixer_init publishes the world-writable cdev; IRQ setup and pcm_register remain fallible after it, and the bad: unwind frees the codec+softc without destroying the cdev - every later unpriv open/ioctl dispatches into freed objects with kobj indirect dispatch. bad: also releases the MEMORY resources before bus_teardown_intr (unmapped-MMIO window). Family precedent severity; untestable on the guest (no NM256 model). fix.diff builds -Werror on guest. Reorder-attach + fix-unwind-order fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4610 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 865 B | β raw | |
| VERDICT.md | β | 4.0 KB | β raw | |
| neomagic_probe.c | β | 2.0 KB | view raw | |
| build.sh | β | 111 B | view raw | |
| run.sh | β | 124 B | view raw | |
| build.log | β | 8.1 KB | view raw | |
| run.log | β | 824 B | view raw | |
| env.txt | β | 1.0 KB | view raw | |
| fix.diff | β | 1.9 KB | view raw |
DF-4610 β neomagic attach-failure paths after mixer_init() leave a live 0666 /dev/mixerN over freed codec+softc (DF-4156 family, 13th instance).
BUILD (guest) cc -O -Wall -o /tmp/neomagic_probe /tmp/neomagic_probe.c
RUN (guest, any user) /tmp/neomagic_probe pciconf -l | grep 10c8 # absent on this guest (no NM256 in QEMU 7.2)
EXPECTED Probe prints the 4 trigger preconditions and exits 0; pciconf shows no 10c8:8005/8006 function. The UAF itself cannot be exercised here: it needs real NM256 hardware whose attach succeeds through mixer_init() and then fails at the IRQ/pcm_register step. Source chain and unwind walk-through: VERDICT.md.
FIX VALIDATION (build-only, on guest /usr/src copy) cd /usr/src && patch -p1 < fix.diff cd sys/dev/sound/driver/neomagic && make # -Werror, clean (see build.log)
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:
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:
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.diffapplied to the guest's/usr/srcandsnd_neomagic.korebuilt with-Werrorβ clean (build.log; canonicalmakein 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.
Fix verification
not_testablefix.diff reorders attach (IRQ, pcm_register, then codec/mixer with mixer_init() checked and codec destroyed inline on its failure) and corrects the bad: unwind order; git apply --check clean and snd_neomagic.ko rebuilds with -Werror on the guest (build.log). Baseline vs patched behaviour is not observable on the audit guest: no NM256 function exists there (QEMU 7.2 has no such device model), so neither the buggy nor the fixed path can attach.
findings/poc/DF-4610/fix.diff; findings/poc/DF-4610/build.log (patched module compile+link, -Werror); canonical unwind at sound.c pcm_unregister (mixer_uninit before codec teardown)
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-4610/VERDICT.md (source chain + unwind walk); neomagic_probe.c/run.log (trigger preconditions + guest hardware absence); env.txt (pciconf: only hdac0 8086:2668, QEMU 7.2.22 model list); fix.diff + build.log (patched snd_neomagic.ko builds clean with -Werror on guest /usr/src)
PoC changes
authored fresh probe (no seed); probe documents the absent trigger environment rather than the UAF itself
Verified recommended fix
Move IRQ setup and pcm_register() before AC97_CREATE/mixer_init, destroy the codec inline when mixer_init() fails, so no failure path exists after the mixer is published; fix bad: resource-release order
Verdict
nm_pci_attach() publishes the 0666 /dev/mixerN at mixer_init() (neomagic.c:695; make_dev+si_drv1=m at mixer.c:730-733, m->devinfo=sc) and keeps two failure paths after it: IRQ allocation/snd_setup_intr failure (neomagic.c:697-703) and pcm_register() failure (neomagic.c:709, EINVAL/ENODEV per sound.c:1021/1027). bad: (neomagic.c:716-723) then ac97_destroy()s the codec (kobj_delete + kfree, ac97.c:852-858) and kfrees sc while the mixer cdev still dispatches open/ioctl into them -- use-after-free with kobj indirect dispatch, 13th instance of the DF-4156/4193/4299/4334/4344/4421/4457/4468/4504/4537/4550 family. Untested rather than demonstrated: the trigger needs a real NeoMagic 10c8:8005/8006 function whose attach reaches mixer_init() and then fails; the audit guest runs QEMU 7.2.22 which has no NM256 device model (only AC97/ES1370/intel-hda/sb16 -- env.txt full pciconf shows only hdac0), so no snd_neomagic attach can occur there at all. Source chain is fully walked in VERDICT.md. Also fixed by the diff: bad: released the MEMORY resources before bus_teardown_intr (neomagic.c:718-720), leaving a window where the handler touches unmapped MMIO.
No comments yet.