β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4610

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4610 Β· 9 files
FileTypeDescriptionSize
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)

VERDICT.md
↓ download 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)

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

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.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)
↓ fix.diffper-fix-DF-4610

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.