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

ess_attach() pcm_register-failure unwind kfrees sc while the 0666 /dev/mixerN that mixer_init() published survives β€” dangling mixer devinfo UAF on open/ioctl (DF-4156 family, 8th instance)

Summary

mixer_init at :1045 publishes the 0666 /dev/mixerN with devinfo == sc; if pcm_register at :1052 fails, the no: unwind frees sc without mixer_uninit (newbus never runs detach for a failed attach). Every subsequent unpriv open+ioctl dispatches through the freed softc including port I/O via bus-space pointers read from freed heap. Trigger = half-failed attach on ESS Solo-1 hardware (absent on guest - inconclusive, family-consistent). mixer_uninit-in-unwind fix (EBADF-safe).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4468 Β· 8 files
FileTypeDescriptionSize
trigger.c β€” 2.7 KB view raw
build.sh β€” 81 B view raw
run.sh β€” 102 B view raw
build.log β€” 198 B view raw
run.log β€” 157 B view raw
env.txt β€” 411 B view raw
VERDICT.md β€” 3.5 KB ↓ raw
fix.diff β€” 477 B view raw
VERDICT.md
↓ download raw

DF-4468 VERDICT β€” snd_solo dangling /dev/mixerN after failed attach (8th family instance)

Status: INCONCLUSIVE (code-proven; not exercisable on this guest)

The audit guest has no ESS Solo-1 PCI function (QEMU emulates es1370, not ES1938/ES1946), and the failure precondition (pcm_register() failing after mixer_init() succeeded) requires either a feeder-subsystem veto flag or

PCMMAXUNIT sound units β€” neither injectable at runtime on stock hardware. Same verification posture as family instances DF-4421 (ich) and DF-4457 (cmi).

Root cause (path:line)

  1. sys/dev/sound/pci/solo.c:1045 β€” mixer_init(dev, &solomixer_class, sc) is called BEFORE pcm_register (line 1052).
  2. sys/dev/sound/pcm/mixer.c:730-733 β€” mixer_init() immediately does make_dev(&mixer_ops, ..., 0666, "mixer%d", unit) and stores pdev->si_drv1 = m; snddev->mixer_dev = pdev; where the kobj mixer m keeps m->devinfo = sc (mixer.c:616-649, mixer_obj_create).
  3. sys/dev/sound/pci/solo.c:1052-1053 β€” if pcm_register(dev, sc, 1, 1) fails, goto no runs ess_release_resources(sc, dev).
  4. sys/dev/sound/pci/solo.c:878 β€” kfree(sc, M_DEVBUF). No mixer_uninit() runs: the only caller of mixer_uninit is pcm_unregister (sound.c:1205), which newbus never invokes for a failed attach.
  5. Result: /dev/mixerN remains a registered world-writable cdev whose kobj devinfo points into freed memory. First open()+ioctl() by any local user reaches essmix_init/essmix_set/essmix_setrecsrc (solo.c:651/668/728) β†’ mix_getdevinfo(m) (mixer.c:616) returns the freed sc β†’ ess_setmixer() (solo.c:241) reads sc->sb (a struct resource *) from freed heap and performs port I/O via rman_get_bustag(sc->sb). UAF-read β†’ forged bus_space handle β†’ out-of-bounds port I/O; realistic outcome is kernel fault / DoS, with heap-grooming dependent potential for worse.

Why pcm_register can fail (solo.c:1052)

  • sound.c:1021-1025 β€” pcm_veto_load set (feeder subsystem error at boot, sound.c:50 / feeder.c:126).
  • sound.c:1027-1033 β€” device unit > PCMMAXUNIT.

Both are operator/boot dependent; not attacker-triggerable on demand β€” hence Low severity, consistent with the 7 filed family instances.

Fix

Match the family fix: unwind the mixer before freeing resources β€”

--- a/sys/dev/sound/pci/solo.c
+++ b/sys/dev/sound/pci/solo.c
@@ -1049,6 +1049,8 @@ ess_attach(device_t dev)
            rman_get_start(sc->io), rman_get_start(sc->sb), rman_get_start(sc->vc),
        rman_get_start(sc->irq),PCM_KLDSTRING(snd_solo));

+   sc->mixer_ok = 1;   /* mark: mixer_init() succeeded */
+
        if (pcm_register(dev, sc, 1, 1))
        goto no;
@@ -1060,6 +1062,9 @@ ess_attach(device_t dev)

 no:
+   if (sc->mixer_ok)
+       mixer_uninit(dev);
        ess_release_resources(sc, dev);
        return ENXIO;
 }

(plus int mixer_ok; in struct ess_info). The systemic fix is moving the mixer_init() call after pcm_register()+pcm_addchan() success across the whole sound family, or making pcm_register failure paths call mixer_uninit.

Attempts

  • Guest run of ./trigger /dev/mixer0 β†’ ENOENT (no ESS hw): run.log.
  • Code-level trace completed as above; no false-positive indicators.

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

not validated on guest (finding itself not guest-reproducible: no ESS hw, failure precondition not injectable); fix.diff is the code-level unwind correction matching the family fix pattern

findings/poc/DF-4468/fix.diff
↓ fix.diffper-fix-DF-4468

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (path:line proof), trigger.c (family-standard mixer ioctl UAF trigger), run.log (guest ENOENT), fix.diff (mixer_uninit on the `no:` unwind - safe for pre-mixer paths since mixer_uninit returns EBADF with no mixer registered)

PoC changes

none (family-standard trigger; seed sketch replaced with complete trigger.c + full precondition analysis)

Verified recommended fix

Call mixer_uninit(dev) on the ess_attach no: unwind before ess_release_resources() frees sc.

Verdict

8th instance of the dangling-/dev/mixerN family: ess_attach() calls mixer_init() (solo.c:1045) which immediately publishes a 0666 /dev/mixerN cdev holding devinfo=sc (mixer.c:730-733); the only post-mixer failure path, pcm_register() failing (solo.c:1052), jumps to no: which kfrees sc (solo.c:878) without mixer_uninit() (only ever called from pcm_unregister, sound.c:1205, which newbus never runs for a failed attach). First open()+SOUND_MIXER_*_VOLUME ioctl by any local user then executes essmix_set/ess_setmixer through the freed softc (solo.c:667/241) including port I/O via pointers read from freed heap. Not reproducible on the audit guest: QEMU does not emulate ES1938/1946 (only es1370) so snd_solo never attaches, and the pcm_register failure precondition (veto-load flag or unit>PCMMAXUNIT) is not runtime-injectable. Code-level trace complete; PoC builds and runs on the guest (ENOENT), same verification posture as family instances DF-4421/DF-4457.