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

t4dwave: attach-failure paths after mixer_init() leave a live 0666 /dev/mixerN over freed codec/tr_info β€” mixer-UAF family (9th instance)

Summary

mixer_init publishes the world-writable mixer cdev before three fallible steps (IRQ setup, dma tag, pcm_register); every failure goto frees the codec and tr_info without mixer_uninit (reachable only via pcm_unregister). Any subsequent unpriv mixer ioctl dispatches kobj methods on the freed codec over the freed tr_info whose lock was also freed. Same precondition class as the whole verified family. Fix is subtle: mixer_uninit itself destroys the codec, so bad: must call mixer_uninit INSTEAD of ac97_destroy (not both). mixinit-flag fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4504 Β· 7 files
FileTypeDescriptionSize
t4dwave_probe.c β€” 820 B view raw
build.sh β€” 111 B view raw
run.sh β€” 100 B view raw
run.log β€” 95 B view raw
fix.diff β€” 1.2 KB view raw
env.txt β€” 449 B view raw
VERDICT.md β€” 2.9 KB ↓ raw
VERDICT.md
↓ download raw

DF-4504 β€” VERDICT

NOT DEMONSTRATED LIVE (hardware-blocked) β€” code-certain UAF, established 9-member family (DF-4156 maestro, DF-4193 es137x, DF-4299/4334/4344/4421/ 4457/4468). fix.diff applies clean; runtime fix validation impossible on this guest.

Root cause (path:line)

  • t4dwave.c:880-882 β€” AC97_CREATE() then mixer_init(dev, ac97_getmixerclass(), codec); mixer.c:730-733 does make_dev(&mixer_ops, ..., 0666, "mixer%d") + si_drv1 = m with m->devinfo = codec (mixer.c:635 via mixer_obj_create).
  • Still-failing paths after that point: t4dwave.c:887 (IRQ alloc / snd_setup_intr), t4dwave.c:920-922 (bus_dma_tag_create), t4dwave.c:928-929 (pcm_register).
  • t4dwave.c:937-945 bad: β€” ac97_destroy(codec) (938: kobj_delete of the method table + snd_mtxfree + kfree, ac97.c:852-861) and kfree(tr) (944), but the mixer cdev is never destroyed: mixer_uninit() is called only from pcm_unregister() (sound.c:1205), unreachable on failed attach.
  • Post-failure, any user opening the surviving world-writable /dev/mixerN and issuing a mixer ioctl runs the kobj method dispatch (ac97mix_set/recsrc -> tr_wrcd/tr_rdcd) on freed codec + freed tr_info (whose tr->lock was also snd_mtxfreed at 943): UAF read/ write and indirect-dispatch control-flow potential.

Why not reproduced

The audit guest's QEMU emulates no Trident 4DWave DX/NX (1023:2000/2001), SiS 7018 (1039:7018) or ALi M5451 (1102:5451) audio controller; snd_t4dwave.ko exists in /boot/kernel but tr_pci_probe() returns ENXIO for every emulated PCI id, so no /dev/mixerN from this driver exists (run.log). Additionally the trigger itself requires a partially failing attach (IRQ/dma-tag/pcm_register failure after a successful mixer_init) β€” a boot-time resource anomaly an unprivileged user cannot create on demand. Both limitations are shared with the filed family (DF-4156/DF-4193 were verified "untested" for the same reasons). A logic-level harness cannot honestly demonstrate a hardware-lifetime UAF, so no harness was faked.

Fix

fix.diff: track mixinit; on bad:, if the mixer was created call mixer_uninit(dev) β€” which destroys the cdev, the snd_mixer, and the codec itself (ac97mix_uninit -> ac97_destroy, ac97.c:1027-1039, mirroring the pcm_unregister order at sound.c:1205) β€” otherwise ac97_destroy(codec) as today. git apply --check clean against sys/. Note the subtlety this fixes correctly: calling both mixer_uninit and ac97_destroy would double-free the codec (mixer_uninit already destroys it).

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

No trigger environment on the audit guest (no 4DWave-family controller; QEMU cannot emulate it; attach-failure precondition not creatable on demand), so baseline vs patched behaviour cannot be observed. fix.diff applies cleanly (git apply --check) and mirrors the pcm_unregister() unwind order.

findings/poc/DF-4504/fix.diff (git apply --check clean); sound.c:1205 canonical order
↓ fix.diffper-fix-DF-4504

Confirmed kernel references

Detail

Evidence (decisive lines)

t4dwave.c:882 vs bad:-path 937-945 (mixer never uninitialized); mixer.c:730-733 (0666 make_dev + si_drv1=m, m->devinfo=codec); ac97.c:852-861 + ac97.c:1027-1039 (ac97_destroy frees the kobj; ac97mix_uninit shows mixer_uninit owns codec destruction); sound.c:1205 (mixer_uninit only via pcm_unregister); run.log (no t4dwave/mixer on guest)

PoC changes

authored fresh hardware-presence probe (no seed)

Verified recommended fix

On the bad: path, mixer_uninit(dev) when mixer_init() succeeded (destroys the 0666 cdev, snd_mixer and codec together); ac97_destroy(codec) only otherwise

Verdict

tr_pci_attach() publishes the 0666 /dev/mixerN (mixer_init, t4dwave.c:882; mixer.c:730-733) before three fallible steps (t4dwave.c:887 IRQ, 920 dma-tag, 928 pcm_register); every one of those gotos lands in bad: which ac97_destroy()s the codec (938, kobj_delete+kfree per ac97.c:852-861) and kfrees tr (944) while the mixer cdev survives β€” mixer_uninit() runs only from pcm_unregister() (sound.c:1205), unreachable on failed attach. Any subsequent unprivileged mixer ioctl dispatches kobj methods on the freed codec into tr_rdcd/tr_wrcd on the freed tr_info (whose lock was also freed at 943): UAF with indirect-dispatch potential. 9th instance of the filed family (DF-4156 maestro, DF-4193 es137x, DF-4299/4334/4344/4421/4457/4468). Untested because the audit guest has no Trident/SiS/ALI 4DWave controller (run.log: no /dev/mixer*, no matching PCI ids; QEMU cannot emulate this hardware) and the precondition is a boot-time attach failure not creatable on demand β€” identical blockers to DF-4156/DF-4193.