cs4281: attach-failure path after mixer_init() leaves a live 0666 /dev/mixerN over freed codec/softc - mixer-UAF family (10th instance)
Summary
mixer_init's return is ignored AND pcm_register remains fallible after it publishes the world-accessible mixer cdev; on pcm_register failure the bad: unwind ac97_destroys the codec and kfrees the softc while the cdev survives (only pcm_unregister ever destroys it). Every mixer ioctl then dispatches through freed codec kobj + freed softc. Identical to the 9 previously-filed instances. Register-first + bad_unregister fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4537 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.2 KB | β raw | |
| VERDICT.md | β | 3.7 KB | β raw | |
| cs4281_probe.c | β | 2.0 KB | view raw | |
| build.sh | β | 133 B | view raw | |
| run.sh | β | 138 B | view raw | |
| build.log | β | 94 B | view raw | |
| run.log | β | 428 B | view raw | |
| env.txt | β | 604 B | view raw | |
| wedge_episode.txt | β | 777 B | view raw | |
| fix.diff | β | 1.3 KB | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| verdict.json | β | 4.0 KB | view raw |
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).
DF-4537 VERDICT β untested (hardware-gated), analysis certain
Finding shape (source chain, path:line)
cs4281_pci_attach()callsmixer_init(dev, ac97_getmixerclass(), codec)at cs4281.c:845 with the return value ignored.mixer_init()publishes a world-accessible cdev: mixer.c:730-733make_dev(&mixer_ops, PCMMKMINOR(unit,0,0), UID_ROOT, GID_WHEEL, 0666, "mixer%d", unit); pdev->si_drv1 = m;wherem->devinfois theAC97_CREATEd codec (cs4281.c:841) whose owndevinfois the driver softcsc.- The next fallible call is
pcm_register(dev, sc, 1, 1)at cs4281.c:847-848 βgoto bad. bad:(cs4281.c:860-873) runsac97_destroy(codec)(ac97.c:852-858:kobj_delete(codec->methods)+kfree(codec)) andkfree(sc)(cs4281.c:873), then returns ENXIO.- Nothing destroys
/dev/mixerN: the only path ispcm_unregister()(sound.c:1205 βmixer_uninit(), mixer.c:795-796destroy_dev(pdev)), which requires a successfulpcm_register(). Newbus does not call detach after a failed attach, so the cdev survives until reboot. - Any user then
open("/dev/mixerN")+ioctl(MIXER_READ/WRITE)βmixer_ioctlβac97mixer_*βAC97_READ/WRITE(codec)β kobj dispatch through the freedcodec->methods/codec->devinfoβ heap UAF (read/write of freed objects; the dispatchedcs4281_rdcd/wrcddereference the freed softc's bus_space tag/handle).
pcm_register() failure preconditions (sound.c:1021-1033):
pcm_veto_load (feeder-init anomaly at boot) or unit > PCMMAXUNIT(255).
Both are boot-time anomalies an unprivileged user cannot create on demand;
they are the same gates the family instances DF-4156β¦DF-4504 documented.
Why untested (not reproduced / not false-positive)
- Trigger hardware absent: the audit QEMU guest has no vendor-1013
(Cirrus) PCI function (run.log:
PCI-ABSENT), socs4281_pci_probe()returns ENXIO and attach β hencemixer_init()β never executes.snd_cs4281.koexists in /boot/kernel and loads, but registers no device. - The
pcm_register()failure itself is a boot-time anomaly (veto/unit-overflow), not creatable by an unprivileged user. - Static chain is complete and line-verified (above); nothing in it is speculative. Classification: untested, confidence certain, impact none demonstrated on this guest.
Environment-experiment note (why no kldload experiments in run.log)
An exploratory kldload snd_cs4281 (no hardware match) followed by
kldunload on the pre-reset guest wedged it in DDB:
panic: sbuf_delete called with uninitialized or corrupt sbuf via
sndstat_sysuninit() at sound-module unload β the already-filed
DF-4116 (sound core, not this file). The guest was reset
(vm.sh reset with-src) and the final probe run was performed clean,
without module loads. Excerpt preserved in wedge_episode.txt.
Fix validation
fix.diff (this pack) reorders pcm_register() before codec/mixer
creation and unwinds via pcm_unregister() on every later failure;
git apply --check passes. Baseline-vs-patched behaviour is not
observable on this guest for the same hardware/precondition absence
(no 1013:6005 function; pcm_register failure not creatable), so
fix_status = not_testable. Canonical unwind order demonstrated by
sound.c:1205 (pcm_unregister does mixer_uninit before codec teardown).
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_testablefix.diff reorders pcm_register() ahead of codec creation/mixer publication, checks mixer_init() and both pcm_addchan() returns, and adds a bad_unregister: unwind via pcm_unregister() (which performs mixer_uninit before codec teardown, cf. sound.c:1205); git apply --check is clean. Baseline vs patched behaviour is not observable on the audit guest for the same hardware/precondition absence (no 1013:6005 function, pcm_register failure not creatable).
findings/poc/DF-4537/fix.diff; canonical unwind order shown at sound.c:1205 (pcm_unregister does mixer_uninit before codec teardown)
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-4537/VERDICT.md (full source chain); cs4281.c:845 mixer_init ignored vs cs4281.c:847 pcm_register check vs cs4281.c:860-873 bad: ac97_destroy+kfree; mixer.c:730-733 make_dev 0666 + si_drv1=m(devinfo=codec); ac97.c:852-858 kobj_delete+kfree; sound.c:1021/1027 failure set; sound.c:1205 only pcm_unregister->mixer_uninit destroys the cdev; build.log/run.log/env.txt (guest-side absence probe); wedge_episode.txt (pre-reset kldload/kldunload abort, known DF-4116, not this file)
PoC changes
authored fresh probe (no seed); probe documents the absent trigger environment (no /dev/mixer* nodes, no vendor-1013 PCI function) rather than the UAF itself
Verified recommended fix
Move pcm_register() before AC97_CREATE/mixer_init() and fail attach (pcm_unregister unwind) on mixer_init()/pcm_addchan() error so no failure path exists after the mixer is published
Verdict
cs4281_pci_attach() ignores mixer_init()'s return at cs4281.c:845 (which publishes a 0666 /dev/mixerN whose si_drv1->devinfo is the AC97 codec, mixer.c:730-733) and keeps a fallible call after it: pcm_register() at cs4281.c:847 returns EINVAL/ENODEV when pcm_veto_load is stuck at its boot-time value (sound.c:1021) or when the pcm unit exceeds PCMMAXUNIT=255 (sound.c:1027). On that failure attach jumps to bad: and ac97_destroy(codec) at cs4281.c:862 kobj_delete()s the method table and kfrees the codec (ac97.c:852-858) plus kfree(sc) at cs4281.c:873, while /dev/mixerN still dispatches any user's ioctls into the freed codec/softc -- use-after-free with indirect-dispatch potential, identical to the DF-4156/4193/4299/4334/4344/4421/4457/4468/4504 family (10th instance). Untested rather than demonstrated: the trigger needs a real Cirrus 1013:6005 (CS4281) function whose attach reaches mixer_init and then fails pcm_register; the audit guest has no vendor-1013 PCI function (QEMU has no CS4281 emulation -- env.txt) and both pcm_register failure preconditions are boot-time anomalies an unprivileged user cannot create.
No comments yet.