# DF-4537 — cs4281 attach-failure dangling /dev/mixerN UAF (DF-4156 family, 10th instance)

## What this is
`cs4281_pci_attach()` publishes the world-accessible (0666) `/dev/mixerN`
cdev via `mixer_init()` (cs4281.c:845, return value ignored) **before**
`pcm_register()` (cs4281.c:847). If `pcm_register()` fails, the `bad:`
unwind `ac97_destroy()`s the codec (cs4281.c:862) and kfrees the softc
(cs4281.c:873) — but nothing destroys `/dev/mixerN`: only
`pcm_unregister() → mixer_uninit()` (sound.c:1205, mixer.c:795-796) ever
does, and it cannot run after a failed `pcm_register()`. Newbus does not
run detach for a failed attach, so the cdev dangles until reboot and
dispatches any user's mixer ioctls into the freed codec (`m->devinfo`,
mixer.c:732) whose kobj method table and `devinfo` softc are both freed
(ac97.c:852-858) — kernel heap UAF, identical in shape to the nine
already-filed family instances (DF-4156/4193/4299/4334/4344/4421/4457/
4468/4504).

## Build / run (on a DragonFly guest)
```
./build.sh     # cc -O -Wall -o /root/cs4281_probe /root/cs4281_probe.c
./run.sh       # /root/cs4281_probe ; pciconf -l | grep 1013
```

## Expected output on a machine WITH a CS4281 whose attach fails
`pcm_register()` failure requires one of (sound.c:1021-1033):
 * `pcm_veto_load` stuck non-zero (sound core feeder-init anomaly), or
 * `device_get_unit(dev) > PCMMAXUNIT` (255) — both boot-time anomalies an
   unprivileged user cannot create.
On such a machine `probe:` prints `UNPRIV mixer ioctl OK` for the dangling
`/dev/mixerN`, demonstrating the freed-codec dispatch surface.

## Actual output on this audit guest (2026-09-21)
No vendor-1013 PCI function exists in QEMU, so `snd_cs4281_pci_probe()`
returns ENXIO and attach never runs: the probe documents the absent
trigger environment (see run.log / env.txt). Verdict: **untested** —
analysis-certain, hardware-gated.

## Fix
`fix.diff` reorders `pcm_register()` ahead of codec creation/mixer
publication and fails attach (via `pcm_unregister` unwind) on any later
error, so no failure path can outlive the published mixer. It also checks
`mixer_init()`/`pcm_addchan()` returns (see DF-4539). Applies clean with
`git apply` against sys/ (never applied there by the audit).
