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

fm801_pci_attach failure paths after mixer_init() leave a live 0666 /dev/mixerN over freed codec+softc β€” mixer-UAF family, 14th instance (DF-4156/4193/4299/4334/4344/4421/4457/4468/4504/4537/4550/4610)

Summary

The world-writable mixer cdev is published before three fallible operations remain (IRQ setup, dma tag, pcm_register); the oops: unwind destroys the codec and frees the softc while the cdev still dispatches open/ioctl into them (kobj indirect dispatch UAF). Secondary: oops: releases the MMIO region before teardown_intr - live ISR over an unmapped window. Family precedent severity; FM801 hardware absent from guest. Reorder + fix-unwind-order fix, compile-validated on guest (-Werror).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4651 Β· 10 files
FileTypeDescriptionSize
README.md β€” 1.4 KB ↓ raw
VERDICT.md β€” 5.5 KB ↓ raw
fm801_probe.c β€” 2.5 KB view raw
build.sh β€” 134 B view raw
run.sh β€” 90 B view raw
run.log β€” 2.2 KB view raw
env.txt β€” 210 B view raw
fix.diff β€” 2.1 KB view raw
fix_validate.sh β€” 830 B view raw
fix_build.log β€” 8.3 KB view raw

DF-4651 β€” fm801 attach-failure mixer-UAF (DF-4156 family, 14th instance)

fm801_pci_attach() publishes the 0666 /dev/mixerN at mixer_init() (fm801.c:620) and keeps three failure paths after it β€” IRQ setup (:625-627), dma tag (:636-638), pcm_register (:646). oops: then ac97_destroy()s the codec (:657) and kfrees the softc (:662) while the mixer cdev still dispatches open/ioctl into them: use-after-free with kobj indirect dispatch. Secondary: oops: releases the register window (:658) before bus_teardown_intr (:659) β€” live handler over unmapped MMIO.

Build (guest, root)

cd /root/df801 && sh build.sh
# cc -O -Wall -o /tmp/fm801_probe fm801_probe.c ; runs the probe +
# captures pciconf/sndstat evidence

Run

sh run.sh    # probe + full pciconf -l

Expected output

The probe prints the trigger preconditions and this guest's hardware absence:

NO 1319 PCI function on this guest
no sndstat

The UAF itself needs a real FM801 (1319:0801) card whose attach passes mixer_init() then fails late β€” impossible under QEMU 7.2.22 (no FM801 device model). The source chain is walked line-by-line in VERDICT.md.

Fix validation

sh fix_validate.sh
# patches the guest /usr/src copy with fix.diff, builds snd_fm801.ko
# with the full kernel warning set (incl. -Werror) -- compiles clean;
# restores the pristine source afterwards (md5-verified).
VERDICT.md
↓ download raw

DF-4651 β€” fm801: attach failure paths after mixer_init() leave a live 0666 /dev/mixerN over freed codec+softc β€” mixer-UAF family, 14th instance

Verdict: source chain fully walked (certain); runtime NOT testable on the audit guest (no FM801 hardware model in QEMU 7.2.22). Family precedent: DF-4156/4193/4299/4334/4344/4421/4457/4468/4504/4537/4550/4610.

Root cause β€” full source chain

sys/dev/sound/pci/fm801.c, fm801_pci_attach():

  1. The mixer is published early. After the register space is mapped (fm801.c:587-611) and the codec brought up:
  • fm801.c:617 β€” codec = AC97_CREATE(dev, fm801, fm801_ac97);
  • fm801.c:620 β€” if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto oops;

mixer_init() creates the world-writable mixer cdev and binds the codec to it: make_dev(&mixer_ops, PCMMKMINOR(unit,0,0), UID_ROOT, GID_WHEEL, 0666, "mixer%d", unit); pdev->si_drv1 = m; (sys/dev/sound/pcm/mixer.c:729-733), with m->devinfo = codec (the AC97_CREATE product). From this point any user can open/ioctl /dev/mixerN and dispatch into fm801 + codec.

  1. Three failure paths remain after it:
  • fm801.c:625-627 β€” bus_alloc_resource_any(SYS_RES_IRQ) or snd_setup_intr() fails β†’ "unable to map interrupt" β†’ goto oops
  • fm801.c:636-638 β€” bus_dma_tag_create() fails β†’ "unable to create dma tag" β†’ goto oops
  • fm801.c:646 β€” pcm_register() fails β†’ goto oops (reachable: EINVAL when pcm_veto_load is set, sound.c:1021; ENODEV when PCMMAXUNIT is reached, sound.c:1027)
  1. oops: frees everything the live cdev points at.
  • fm801.c:657 β€” ac97_destroy(codec) β†’ kobj_delete(codec->methods)
  • fm801.c:662 β€” kfree(fm801, M_DEVBUF)

No pcm_unregister()/mixer_uninit() runs on this path (that is the only thing that would retire the mixer cdev), so the 0666 /dev/mixerN stays registered with si_drv1 β†’ freed snd_mixer, m->devinfo β†’ freed ac97_info whose kobj method table is deleted, and the channel ops still reference the freed fm801_info. Any later open/ioctl is a use-after-free with kobj indirect dispatch (attacker-influenced heap reuse β†’ memory corruption; family impact).

  1. Secondary defect on the same unwind: fm801.c:658 releases fm801->reg (the bus space the interrupt handler immediately reads, fm801_intr β†’ fm801_rd(FM_INTSTATUS) at fm801.c:292) before bus_teardown_intr at fm801.c:659 β€” a live-handler window over an unmapped MMIO/IO window.

Trigger preconditions (why untested here)

Requires a ForteMedia FM801 (PCI 1319:0801) audio function whose attach passes mixer_init() (AC97 codec answers the FM_CODEC_CMD protocol, fm801.c:210-238) and then fails at IRQ allocation / dma-tag creation / pcm_register. The audit guest runs QEMU 7.2.22 which has no FM801 device model (only AC97/ES1370/intel-hda/sb16): run.log shows the full pciconf -l with no 1319 vendor function and no sndstat. A hardware-bearing host (or PCI-passthrough of a flaky card, or IRQ-table exhaustion during hotplug) can realize the path.

Fix

fix.diff (validated: compiles clean with the full kernel warning set incl. -Werror as snd_fm801.ko on the guest /usr/src, see fix_build.log):

  • Move the IRQ setup (625-628) and dma-tag creation (630-639) and pcm_register() (646) before AC97_CREATE/mixer_init, so no failure path exists after the mixer cdev is published; mixer_init() failure destroys the codec inline and the oops: path can never free an object a live /dev/mixerN points at.
  • Fix the unwind order: bus_teardown_intr before releasing fm801->reg.

Same shape as the accepted DF-4610 (neomagic) family fix; FreeBSD upstream eliminated the class by making pcm_register void + reordering.

What was checked and is NOT broken (negative results)

  • fm801ch_setspeed (fm801.c:400): loop bound i < 10 over the 11-entry rate table β€” fm801_rates[10] is the sentinel; no OOB.
  • fm801ch_setblocksize/fm801ch_trigger DMALEN/DMABUF2: the pcm core clamps hblksz <= sndbuf_getmaxsize>>1 before calling the driver (sys/dev/sound/pcm/channel.c:1839-1841), bufsz ≀ 65536 (fm801.c:613), so play_nextblk = start + blksize stays inside the buffer and DMALEN fits the 16-bit register. No OOB DMA.
  • fm801ch_setformat rec path uses FM_PLAY_16BIT (0x4000) instead of FM_REC_16BIT (fm801.c:367) β€” same numeric value, benign.
  • AC97 access (fm801.c:210-274): regno bounded by the ac97 core; the 50Γ—10ms busy-polls are a latency wart, not a security defect.
  • fm801_intr NULL-channel window between snd_setup_intr (625) and pcm_addchan (647) requires a spurious PLAY/REC status latch from well-behaved hardware β€” speculative only, not filed.
  • The radio child (fm801.c:651): no "radio" driver exists in this tree, the child never attaches; fm801_alloc_resource only hands out fm801->reg β€” dead path here.
  • Suspend/resume use bus_generic_* only β€” functional gap (device not re-initialized after resume), no memory-safety impact.
  • ar2316GetChannelMaxMinPower-style interpolation in the ath file was checked separately (see the ar2316 findings' packs).

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 reorders attach (IRQ, dma tag, pcm_register, then codec/mixer with mixer_init() checked and codec destroyed inline on its failure) and corrects the oops: release order (teardown_intr before reg release); compiles and links clean as snd_fm801.ko with the full kernel warning set incl. -Werror on the guest /usr/src (fix_build.log), pristine source restored md5-verified. Baseline vs patched behaviour is not observable on the audit guest: no 1319:0801 function exists there (QEMU 7.2 has no FM801 device model), so neither the buggy nor the fixed path can attach.

findings/poc/DF-4651/fix.diff; fix_build.log (patched module compile+link with -Werror, artifact snd_fm801.ko + fm801.o listed, restore md5s match); canonical unwind at sound.c pcm_unregister (mixer_uninit before codec teardown)
↓ fix.diffper-fix-DF-4651

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-4651/VERDICT.md (source chain + unwind walk + negative results); fm801_probe.c/run.log/env.txt (trigger preconditions + guest hardware absence); fix.diff + fix_build.log (patched snd_fm801.ko builds clean with -Werror on guest /usr/src; artifact list + pristine-restore md5 proof appended)

PoC changes

authored fresh probe (no seed); probe documents the absent trigger environment rather than the UAF itself; fix_validate.sh builds the patched module on the guest and md5-verifies the pristine restore

Verified recommended fix

Move IRQ setup, dma-tag creation and pcm_register() before AC97_CREATE/mixer_init, destroy the codec inline when mixer_init() fails, and tear the handler down before unmapping MMIO in oops: (see fix.diff)

Verdict

fm801_pci_attach() publishes the 0666 /dev/mixerN at mixer_init() (fm801.c:620; make_dev+si_drv1=m at mixer.c:729-733, m->devinfo=codec) and keeps three failure paths after it: IRQ allocation/snd_setup_intr failure (fm801.c:625-627), bus_dma_tag_create failure (fm801.c:636-638) and pcm_register() failure (fm801.c:646; EINVAL/ENODEV per sound.c:1021/1027). oops: (fm801.c:656-663) then ac97_destroy()s the codec (kobj_delete + kfree, ac97.c:851-858) and kfrees fm801 while the mixer cdev still dispatches open/ioctl into them -- use-after-free with kobj indirect dispatch, 14th instance of the DF-4156/4193/4299/4334/4344/4421/4457/4468/4504/4537/4550/4610 family. Secondary defect folded into the fix: oops: released fm801->reg (fm801.c:658) before bus_teardown_intr (fm801.c:659), leaving a live fm801_intr window over unmapped MMIO (fm801_intr reads FM_INTSTATUS at fm801.c:292 immediately). Untested rather than demonstrated: the trigger needs a real ForteMedia 1319:0801 function whose attach reaches mixer_init() and then fails; the audit guest runs QEMU 7.2.22 which has no FM801 device model (run.log: full pciconf -l shows no 1319 vendor function; no sndstat). Source chain fully walked in VERDICT.md. Fix compile-validated on guest /usr/src: patched snd_fm801.ko builds with the full kernel warning set incl. -Werror (fix_build.log); pristine source restored (md5-verified).