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

snd_vibes: sv_attach failure paths after mixer_init leave a live 0666 /dev/mixerN over released bus resources and leak sc (DF-4156 family, 12th instance)

Summary

mixer_init publishes the 0666 mixer cdev whose devinfo is the softc; four later failure paths goto fail which destroys the DMA tag and releases ALL bus resources but never calls mixer_uninit (only reachable via pcm_unregister, never run for a failed attach). The world-RW mixer survives over cached bus_space handles now pointing at rman-released I/O ports - unpriv MIXER_WRITE drives bus_space I/O on ports rman may have reassigned to another device; sc also leaked on every fail path. mixer_uninit-in-unwind + kfree fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4552 Β· 11 files
FileTypeDescriptionSize
README.md β€” 3.1 KB ↓ raw
VERDICT.md β€” 4.2 KB ↓ raw
mixer_probe.c β€” 2.1 KB view raw
build.sh β€” 94 B view raw
run.sh β€” 106 B view raw
build.log β€” 181 B view raw
run.log β€” 743 B view raw
env.txt β€” 418 B view raw
fix.diff β€” 1.3 KB view raw
manifest.json β€” 1.1 KB view raw
verdict.json β€” 2.7 KB view raw

DF-4552 β€” snd_vibes attach-failure paths leave a live 0666 /dev/mixerN over released bus resources (DF-4156 family, 12th instance)

What this is

sv_attach() (sys/dev/sound/pci/vibes.c) publishes the mixer at line 789 (mixer_init(dev, &sv_mixer_class, sc)), which β€” see sys/dev/sound/pcm/mixer.c:730-733 β€” make_dev()s a mode 0666 /dev/mixerN cdev whose si_drv1->devinfo is the driver softc sc. Four later failure paths

  • vibes.c:815-821 resource-assumption check (goto fail)
  • vibes.c:837-840 dmaa allocation (goto fail)
  • vibes.c:856-859 dmac allocation (goto fail)
  • vibes.c:872-875 pcm_register failure (goto fail)

unwind with fail: (vibes.c:888-901): the DMA tag is destroyed, the interrupt is torn down and all bus resources (enh/dmaa/dmac) are released β€” but mixer_uninit() is never called (pcm_unregister(), the only caller of mixer_uninit β€” sound.c:1205 β€” is never reached on a failed attach, and even a hypothetical later pcm_unregister would return early because PCM_ALIVE(d) is false for a never-registered device). sc itself is also leaked (no kfree, including the early ENXIO return at vibes.c:746-749).

Result: after a post-mixer_init attach failure, /dev/mixerN stays world-RW with devinfo = sc whose cached bus_space_tag/handle pairs point at I/O port ranges rman no longer owns. Any local user (device is 0666 root:wheel) issuing mixer ioctls drives sv_mix_set() β†’ sv_indirect_get/set() β†’ bus_space_read/write_1 on released ports, which rman may have re-allocated to a different PCI device.

This is the 12th instance of the sound-family mixer-UAF/unwind-asymmetry family: DF-4156, DF-4193, DF-4299, DF-4334, DF-4344, DF-4421, DF-4457, DF-4468, DF-4504, DF-4537, DF-4550.

Reproduce (on hardware)

  1. Machine with an S3 SonicVibes (PCI 5333:ca00).
  2. Force sv_attach() to fail after mixer_init() β€” e.g. make the midi/games port-gap assumption fail (vibes.c:815-821) by having the PCI BIOS lay out the MIDI/GAMES BARs such that the gap is >= 0x800 or not 0x200-aligned, or patch a failure injection at vibes.c:874.
  3. As an unprivileged user: cc -O -o mixer_probe mixer_probe.c && ./mixer_probe /dev/mixer0 β€” the MIXER_WRITE ioctl reaches sv_mix_set() over released ports.

What was run on the audit QEMU guest (2026-09-21)

The guest has no SonicVibes device (pciconf -l | grep 5333 β†’ no match), so sv_attach never runs and the failure window cannot be entered. The probe was built on the guest and run anyway, as root and as an unprivileged user, to document the honest result:

/tmp/mixer_probe /dev/mixer0  β†’  open: No such file or directory   (no mixer0 published)
/tmp/mixer_probe /dev/mixer   β†’  open: Bad file descriptor         (default clone; zero pcm units registered β†’ mixer_clone ENODEV path)

with snd_vibes.ko loaded (kldload snd_vibes), /dev/sndstat showing Installed devices: (empty). Logs: build.log, run.log.

Verdict

not_reproduced β€” the defect is certain at source level (same proven family pattern as 11 accepted instances), but this guest has no SonicVibes PCI hardware, so the trigger cannot be entered. See VERDICT.md.

VERDICT.md
↓ download raw

DF-4552 VERDICT β€” not_reproduced on this guest (hardware absent); source-level: certain

Narrative

sv_attach() ordering (sys/dev/sound/pci/vibes.c):

step line effect
alloc sc 730 kmalloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_ZERO) β€” driver-private, freed only in sv_detach
irq setup 763-770 snd_setup_intr(dev, sc->irq, 0, sv_intr, sc, &sc->ih)
mixer_init 789 publishes 0666 /dev/mixerN, m->devinfo = sc (mixer.c:730-733)
dmaa hack 823-848 midi/games port-gap hack; goto fail on assumption violation (815-821) or alloc failure (837-840)
dmac hack 850-867 goto fail on alloc failure (856-859)
pcm_register 872-875 goto fail on failure
fail: 888-901 destroys dmat, tears down irq, releases enh/dmaa/dmac resources β€” no mixer_uninit, no kfree(sc)

mixer_uninit() (mixer.c:767) is only ever called from pcm_unregister() (sound.c:1205). A failed attach means device_is_attached() is false, so detach is never invoked, and even if it were, pcm_unregister returns 0 early because PCM_ALIVE(d) is false for the never-registered device β€” so nothing ever destroys the mixer cdev. Same asymmetry as the 11 accepted family instances (DF-4156 … DF-4550); in the other instances the fail path additionally kfrees the softc making it a heap UAF β€” here the softc leaks, so the live mixer drives bus_space_* on released I/O port resources instead (rman may have re-assigned them).

Post-failure reachability for an unprivileged user: /dev/mixerN is 0666 root:wheel (mixer.c:730-731). MIXER_WRITE β†’ mixer_ioctl β†’ sv_mix_set (vibes.c:541) β†’ sv_gain β†’ sv_channel_gain (vibes.c:483) β†’ sv_indirect_get/set (vibes.c:139/151) using sc->enh_st/enh_sh, which were cached at vibes.c:750-751 from a resource released at vibes.c:896.

Also noted (folded into this finding): the early return ENXIO at vibes.c:746-749 leaks sc outright (no resource cleanup needed there, but no kfree), and every fail: path leaks sc.

Guest run (audit guest, 2026-09-21, uname in env.txt)

  • pciconf -l | grep 5333 β†’ no SonicVibes device present (QEMU/i440fx machine: only Intel PIIX + virtio + Red Hat VGA).
  • kldload snd_vibes succeeds (module loads, probe finds nothing): /dev/sndstat β†’ Installed devices: (empty); no /dev/mixer0 is ever created (mixer_init never runs).
  • ./mixer_probe /dev/mixer0 (root and unprivileged): open: No such file or directory.
  • ./mixer_probe /dev/mixer (the sound-core default clone node, sound.c:1426): open: Bad file descriptor β€” with zero registered pcm devices mixer_clone() (mixer.c:1340-1360) finds no PCM_REGISTERED unit and returns ENODEV; harmless here.
  • Module unloaded again; guest left clean.

Incidental: the second unload cycle (without any cat /dev/sndstat in between) panicked the guest in sndstat_sysuninit() β€” that is the already-known DF-4116 (sndstat.c never-initialized sbuf deleted on sound.ko unload), reproduced live and consistent with its existing reproduced/panic verdict. Not a vibes.c defect; not re-filed here.

Bottom line

  • Source-level defect: certain β€” identical, already-accepted family pattern; all four post-mixer_init failure paths enumerated.
  • On this guest: not_reproduced β€” the PCI device does not exist, so the trigger window cannot be entered at all.
  • Realistic impact ceiling on real hardware: unprivileged mixer ioctls poking I/O ports the driver no longer owns (worst case: another PCI device's registers), plus a permanent softc/mixer leak per failed attach. No kernel heap memory corruption (softc is leaked, not freed).

Fix validation

Not testable on this guest (no hardware). fix.diff is source-reviewed only: mirror the family fix β€” unwind the mixer before releasing resources on every failure path taken after mixer_init(), and free sc.

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 not validated on hardware: guest has no SonicVibes device, so neither baseline nor patched behavior is observable; fix.diff is source-reviewed and git-apply-verified only

fix.diff
↓ fix.diffper-fix-DF-4552

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log sections =B..=I= (pciconf has no 5333 device; mixer_probe ENOENT/EBADF as both root and unprivileged user); VERDICT.md table of the four failure paths; mixer.c:730-733 make_dev 0666; sound.c:1205 mixer_uninit only reachable via pcm_unregister

PoC changes

authored from scratch this pass (no seed; finding filed this pass)

Verified recommended fix

On every failure path after mixer_init call mixer_uninit(dev) before releasing bus resources, and kfree(sc) on all fail paths (see fix.diff)

Verdict

Source-level defect is certain (identical unwind asymmetry to 11 accepted family instances DF-4156..DF-4550): sv_attach publishes the 0666 /dev/mixerN at mixer_init (vibes.c:789) and four later goto-fail paths (815-821, 837-840, 856-859, 872-875) release all bus resources without mixer_uninit, leaving a world-RW mixer whose devinfo (sc) drives bus_space I/O on rman-released ports; sc is also leaked on every fail path and on the early ENXIO at 746-749. Not reproduced on the audit guest because no S3 SonicVibes PCI device exists there: probe built and run as root and unprivileged user, /dev/mixer0 absent (ENOENT), default /dev/mixer clone returns EBADF with zero registered units.