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

emu_pci_attach bad: path never unwinds emu_init β€” leaks ptb_pages/silent_page DMA and mpu401/midi state on every late attach failure

Summary

After emu_init succeeds (allocating the 2KB PTB DMA page and 4KB silent page and wiring mpu401), any later attach failure (AC97_CREATE, mixer_init, IRQ alloc/snd_setup_intr) jumps to bad: which releases reg/irq/dmat/lock and kfrees sc but never calls emu_uninit - orphaning ~6KB of coherent DMA plus mpu401/midi structures per failed attach, repeated on bus rescans. Contrast emu_init's own correct internal unwind. Hardware-gated (no 1102 PCI device on guest, kldload fails). inited-flag unwind fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4094 Β· 8 files
FileTypeDescriptionSize
README.md β€” 2.4 KB ↓ raw
VERDICT.md β€” 2.6 KB ↓ raw
probe.sh β€” 585 B view raw
run.log β€” 282 B view raw
env.txt β€” 184 B view raw
fix.diff β€” 771 B view raw
manifest.json β€” 1006 B view raw
verdict.json β€” 2.4 KB view raw

DF-4094 β€” emu10k1 attach-failure path leaks emu_init DMA allocations + mpu401 state

File: sys/dev/sound/pci/emu10k1.c (pass 2, GLM 5.3)

What

emu_pci_attach() calls emu_init(sc) at sys/dev/sound/pci/emu10k1.c:2114. On success emu_init() has allocated and programmed:

  • sc->mem.ptb_pages β€” EMUMAXPAGES*4 = 2048 bytes of bus_dmamem (emu10k1.c:1891-1892)
  • sc->mem.silent_page β€” 4096 bytes of bus_dmamem (emu10k1.c:1896-1897)

After that, three more steps can still fail and jump to bad::

  • AC97_CREATE() == NULL (emu10k1.c:2119-2120)
  • mixer_init() == -1 (emu10k1.c:2122)
  • IRQ alloc / snd_setup_intr() (emu10k1.c:2127-2133)
  • additionally emu_midiattach() has already run (emu10k1.c:2124), so sc->mpu (kmalloc'd mpu401 + midi unit) is live too.

The bad: path (emu10k1.c:2149-2157) destroys codec/reg/irq/dmat/lock and frees sc, but never calls emu_uninit(), so the two DMA pages (~6 KB of coherent memory per failed attach) and the mpu401/midi structures leak. emu_init()'s own internal failure paths free their allocations correctly (emu10k1.c:1893-1901) β€” the leak is exclusively the late-failure window.

Threat model

Requires an emu10k1/emu10k2/emu10k3 PCI card whose attach fails late (e.g. shared-IRQ resource exhaustion, AC97 codec probe failure). Privileged/hardware-gated; impact is a kernel-memory leak per attach attempt (attach retries on bus rescans repeat the leak). No memory corruption, no info leak to userspace.

Source trace (certain, source-level)

  1. emu10k1.c:2114 if (emu_init(sc) == -1) goto bad; β€” success continues
  2. emu10k1.c:1891-1901 emu_init allocates ptb_pages + silent_page (kept on success)
  3. emu10k1.c:2119-2133 any of AC97_CREATE / mixer_init / irq alloc fails β†’ goto bad
  4. emu10k1.c:2149-2157 bad: frees reg/irq/dmat/lock/sc β€” ptb_pages, silent_page, sc->mpu never released.

Fix

Track that emu_init succeeded and unwind via emu_uninit() in bad: (see fix.diff). emu_uninit() is safe to call in this window: it writes quiescent values to the card (already resources-mapped), frees both DMA pages, and only calls mpu401_uninit if sc->mpu != NULL (emu10k1.c:2038).

Reproduce

Hardware-gated: QEMU does not emulate emu10k1 (probe evidence in env.txt / run.log: no 1102:0002/0004/0008 PCI device, kldload snd_emu10k1 fails). Source-level certainty only.

VERDICT.md
↓ download raw

DF-4094 VERDICT

Status: inconclusive on-guest (hardware-gated) / certain at source level. Impact: kernel heap/DMA leak (~6 KB + mpu401 struct per failed attach).

Was it reproduced?

No. The DragonFly QEMU guest has no Creative emu10k1 device β€” verified live: pciconf -l shows no vendor 0x1102 function, kldload snd_emu10k1 fails (run.log). The attach-failure path cannot be entered without the PCI device, so runtime reproduction is impossible on this guest. The defect itself is proven by source trace:

  • emu_pci_attach() sys/dev/sound/pci/emu10k1.c:2114 β€” emu_init(sc) succeeds and allocates sc->mem.ptb_pages (emu10k1.c:1891) and sc->mem.silent_page (emu10k1.c:1896).
  • Late failures at emu10k1.c:2119 (AC97_CREATE), :2122 (mixer_init), :2127-2133 (IRQ alloc/setup) all goto bad.
  • bad: at emu10k1.c:2149-2157 releases reg/irq/dmat/lock and kfrees sc but never calls emu_uninit() (which is the only code that frees ptb_pages/silent_page β€” emu10k1.c:2035-2036 β€” and mpu401_uninit at :2038-2039, while emu_midiattach() ran at :2124).
  • emu_init's own failure paths (emu10k1.c:1893-1901) free properly, so the leak is exactly the post-emu_init failure window.

Why not exploitable further

Pure allocation leak; no use-after-free (the leaked blocks are simply orphaned), no info leak, no attacker-controlled size. Trigger needs the physical card AND a late attach failure (resource exhaustion). Severity Low.

Fix validation

Not performed on-guest (would require emu10k1 hardware to build a kernel and fail an attach deliberately). fix.diff is a straightforward unwind fix:

int inited = 0;
...
if (emu_init(sc) == -1) goto bad;
inited = 1;
...
bad:
    if (inited)
        emu_uninit(sc);   /* frees ptb_pages, silent_page, mpu */

emu_uninit() is safe here: card registers are still mapped until the bus_release_resource that follows, EMU_INTE is zeroed first, and mpu401_uninit is guarded by if (sc->mpu).

kernel_refs

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 and git-apply-verified against the read-only sys/ tree; cannot be built+validated on the guest because no emu10k1 device exists to attach (and thus fail late).

fix.diff (unified, applies clean: 'git apply --check' OK)
↓ fix.diffper-fix-DF-4094

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (4-step source trace with emu10k1.c line refs); run.log (guest probe: pciconf grep 1102 empty, kldload fails); fix.diff (git apply --check clean, adds inited flag + emu_uninit() at bad:)

PoC changes

No PoC seed existed; wrote probe.sh documenting the guest-side hardware-absence check and ran it.

Verified recommended fix

Track emu_init success and call emu_uninit(sc) in the bad: path before releasing resources.

Verdict

Source-level certain, runtime-gated: emu_pci_attach() succeeds in emu_init() (which allocates ptb_pages + silent_page DMA at emu10k1.c:1891/1896 and wires mpu401 at :2124), and every later failure (AC97_CREATE :2119, mixer_init :2122, IRQ alloc :2127-2133) jumps to bad: (:2149-2157) which never calls emu_uninit() β€” leaking ~6KB coherent DMA + struct mpu401 per failed attach. The QEMU guest has no emu10k1 device (probe: no 0x1102 PCI function, kldload snd_emu10k1 fails), so the path cannot be entered at runtime; classified inconclusive-on-guest rather than not_reproduced.