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

emu_pcm_attach late-failure path frees sc while the mixer remains registered: dangling mixer devinfo UAF on /dev/mixer ioctls and leaked AC97 submixer

Summary

After mixer_init succeeds, pcm_register can still fail (veto/unit exhaustion). The shared bad: label kfrees sc without mixer_uninit: the registered mixer keeps devinfo==freed sc, so the next /dev/mixerN ioctl runs emu_dspmixer_set on freed memory (route reads, sc->sm deref, indirect writes through a freed sc->card), and the mix_create'd AC97 submixer leaks. Distinct sink from DF-1506 (dangling IRQ consumer on the same path). Mixer-uninit-on-bad fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4299 Β· 5 files
FileTypeDescriptionSize
README.md β€” 1.3 KB ↓ raw
VERDICT.md β€” 2.9 KB ↓ raw
fix.diff β€” 2.0 KB view raw
manifest.json β€” 542 B view raw
verdict.json β€” 1.7 KB view raw

DF-4299 β€” emu10kx-pcm attach failure after successful mixer_init frees live mixer devinfo

Finding

emu_pcm_attach() (sys/dev/sound/pci/emu10kx-pcm.c) calls mixer_init(dev, &emudspmixer_class, sc) (line 1399) β€” registering the mixer with sc as its persistent devinfo β€” and only afterwards performs steps that can still fail (emu_pcm_init() is a no-op that always returns 0, but pcm_register() at line 1467 can fail with EINVAL/ENODEV). The shared bad: label (lines 1488-1494) then frees sc (and, for RT_FRONT, the AC97 submixer created inside emu_dspmixer_init() at line 214 via mixer_create()) without calling mixer_uninit():

  • every later /dev/mixerN ioctl dereferences the freed sc through mix_getdevinfo(m) β†’ emudspmixer_set() β†’ sc->card, sc->sm, emumix_set_volume(sc->card, ...) β†’ use-after-free read/write;
  • the sc->sm AC97 submixer object leaks.

Distinct from the known DF-1506 (which covers the dangling IRQ consumer sc->ihandle registered at line 1453 on the same path).

Status

Not verified on the QEMU guest: snd_emu10kx_pcm requires SB Live!/ Audigy PCI hardware (CA0108/EMU10K2), which the guest does not emulate. Analysis-only; root cause established by code path trace.

Fix

On the bad: path, run mixer_uninit(dev) (when mixer_init succeeded) before freeing sc.

VERDICT.md
↓ download raw

DF-4299 verdict

Reproduced?

No β€” not testable on this guest. snd_emu10kx_pcm binds to the emu10kx bridge driver, which probes for Creative EMU10K1/EMU10K2 PCI chipsets (emu10kx.c PCI IDs). The audit guest (QEMU virtio/ATI-less) has no such device; the module has nothing to attach to, so the trigger cannot be reached at runtime here (kldload snd_emu10kx would simply find no hardware). Status: untested, analysis-only.

Root cause (path:line)

  • sys/dev/sound/pci/emu10kx-pcm.c:1399 β€” mixer_init(dev, &emudspmixer_class, sc) registers sc as the mixer's persistent devinfo (sound.c mixer_init stores it; mix_getdevinfo() returns it to every mixer ioctl).
  • sys/dev/sound/pci/emu10kx-pcm.c:214 β€” for RT_FRONT the mixer init callback also mixer_create()s an AC97 submixer stored in sc->sm.
  • sys/dev/sound/pci/emu10kx-pcm.c:1455-1470 β€” after mixer_init, only emu_pcm_init() (always returns 0, line 1285-1290) and pcm_register() (line 1467) can still fail; pcm_register fails on pcm_veto_load (sound.c:1021-1027) or device_get_unit(dev) > PCMMAXUNIT (sound.c:1030-1035).
  • sys/dev/sound/pci/emu10kx-pcm.c:1488-1494 β€” bad: path: ac97_destroy(sc->codec) + snd_mtxfree(sc->lock) + kfree(sc) β€” no mixer_uninit(dev), no mixer_delete(sc->sm).

Consequence: /dev/mixerN remains registered (created by the sound core inside mixer_init) with m->devinfo == freed sc; the next mixer ioctl (e.g. MIXER_CMD/OSS mixer write) runs emu_dspmixer_set() on freed memory: reads sc->route, dereferences sc->sm/sc->card, writes DSP registers through emumix_set_volume(sc->card, ...) (sc->card freed pointer load) β†’ kernel UAF (read + effectively arbitrary indirect register-write path), plus the submixer leak.

Threat

Requires the sound card present and an attach-time failure after mixer_init β€” i.e. pcm_register() failing. Not attacker-schedulable in general (unit exhaustion / prior pcm error), hence Low severity, but the resulting UAF is on the standard mixer ioctl path.

Fix

--- a/sys/dev/sound/pci/emu10kx-pcm.c
+++ b/sys/dev/sound/pci/emu10kx-pcm.c
@@ -1485,6 +1485,9 @@ emu_pcm_attach(device_t dev)
    return (0);

 bad:
+   if (sc->ihandle >= 0)
+       (void)emu_intr_unregister(sc->card, sc->ihandle);
+   if (mixer_inited)
+       (void)mixer_uninit(dev);
    if (sc->codec)
        ac97_destroy(sc->codec);
    if (sc->lock)

(with a local int mixer_inited = 0; set to 1 after each successful mixer_init() call; the ihandle unregister line closes the DF-1506 attach-failure half.)

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 authored from the traced root cause; not built (no hardware)

VERDICT.md diff
↓ fix.diffper-fix-DF-4299

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md path trace: emu10kx-pcm.c:1399 (mixer_init), :1467 (pcm_register failure), :1488-1494 (bad: kfree without mixer_uninit); sound.c mixer_init devinfo registration.

PoC changes

n/a β€” no PoC (hardware prerequisite absent)

Verified recommended fix

Run mixer_uninit(dev) on the emu_pcm_attach bad: path when mixer_init succeeded before kfree(sc).

Verdict

Analysis-only: emu_pcm_attach() frees sc on the late-failure (pcm_register) path without mixer_uninit(), leaving /dev/mixerN's devinfo dangling (UAF on the next mixer ioctl) and leaking the AC97 submixer. Not verifiable on the audit guest: snd_emu10kx_pcm requires SB Live!/Audigy PCI hardware the guest does not emulate.