# 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`.

2. **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)

3. **oops: frees everything the live cdev points at.**

   - fm801.c:657 — `ac97_destroy(codec)` → `kobj_delete(codec->methods)`
     + `kfree(codec)` (sys/dev/sound/pcm/ac97.c:851-858)
   - 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).

4. **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.
