cmi_attach error path after mixer_init leaves a live 0666 /dev/mixerN pointing at freed sc β UAF on open/ioctl (DF-4156 family, 7th instance)
Summary
mixer_init at :986 publishes the world-writable /dev/mixerN whose devinfo is sc, before pcm_register at :989; pcm_register can fail (pcm_veto_load EINVAL, PCMMAXUNIT) and the bad: unwind frees sc without mixer_uninit - any unpriv open/ioctl dispatches through freed sc (type-confused bus_space tag+handle). 7th instance of the verified family, narrowest window (one post-mixer goto). Fix compile-validated on guest (patched snd_cmi.ko -Werror RC=0). pcm_register-first + pcm_unregister-on-mixer-fail fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4457 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.4 KB | β raw | |
| VERDICT.md | β | 5.9 KB | β raw | |
| trigger.hw.sh | β | 1.4 KB | view raw | |
| run.sh | β | 164 B | view raw | |
| build.sh | β | 137 B | view raw | |
| build.log | β | 4.7 KB | view raw | |
| run.log | β | 362 B | view raw | |
| guest_check.log | β | 630 B | view raw | |
| env.txt | β | 630 B | view raw | |
| fix.diff | β | 948 B | view raw | |
| manifest.json | β | 1.2 KB | view raw | |
| verdict.json | β | 4.3 KB | view raw |
DF-4457 β cmi_attach error path after mixer_init leaves a live 0666 /dev/mixerN pointing at freed sc (UAF on open/ioctl)
7th instance of the DF-4156/4193/4299/4334/4344/4421 sound-driver family,
in sys/dev/sound/pci/cmi.c.
Reproduce (on hardware with a CMI8738-class card)
sh trigger.hw.shβ preconditions and expected dangling-cdev state.- Success criterion: after a failed snd_cmi attach that got past
mixer_init(),ls -l /dev/mixerNstill shows the 0666 cdev and any unprivilegedopen+ioctl(MIXER_WRITE(...))dispatches through the freed softc (garbage bus_space reads/writes / panic under INVARIANTS).
On the audit QEMU guest
sh run.sh β proves the gate: no CMedia/audio PCI device, snd_cmi.ko
cannot load (midi dependency mismatch). Runtime reproduction is
impossible on this guest; the defect is proven by source trace
(VERDICT.md).
Build (fix compile-validation)
sh build.sh (inside guest, with the fixed cmi.c staged at
/root/cmi.fixed.c) β baseline and fix both build -Werror clean.
Files
VERDICT.mdβ full narrative, root-cause table, negative resultstrigger.hw.shβ real-hardware triggerrun.sh+run.log/guest_check.logβ guest hardware-gate proofbuild.sh+build.logβ pristine & patched module buildsfix.diffβ git-apply-able unified diff (validated: applies clean, compiles -Werror clean as a module on the guest)env.txtβ guest environment
DF-4457 β PoC Verification Verdict
File: sys/dev/sound/pci/cmi.c (pass 2)
Family: DF-4156 (maestro) / DF-4193 (es137x) / DF-4299 (emu10kx) /
DF-4334 (via8233) / DF-4344 (atiixp) / DF-4421 (ich) β 7th instance.
Guest: DragonFly 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026
(X86_64_GENERIC, INVARIANTS ON, no SMAP/SMEP/KASLR), cc 8.3.
Verdict: INCONCLUSIVE at runtime (code defect Certain; hardware-gated)
Root cause (source-trace proof)
cmi_attach() calls mixer_init(dev, &cmi_mixer_class, sc) at
cmi.c:986 and pcm_register(dev, sc, 1, 1) at cmi.c:989.
mixer_init() (mixer.c:687-764) creates a world-writable cdev:
mixer.c:730 pdev = make_dev(&mixer_ops, PCMMKMINOR(unit,0,0), mixer.c:731 UID_ROOT, GID_WHEEL, 0666, "mixer%d", unit); mixer.c:732 pdev->si_drv1 = m; /* m->devinfo = sc (mixer.c:78) */
If pcm_register() fails it returns non-zero (EINVAL via
pcm_veto_load, sound.c:1021; ENODEV via unit > PCMMAXUNIT=255,
sound.c:1027) and cmi_attach goto's bad: (cmi.c:1004-1016), which
destroys the dma tag, tears down the irq, releases reg/irq resources,
snd_mtxfree(sc->lock) and kfree(sc, M_DEVBUF) β without
mixer_uninit(dev). The 0666 /dev/mixerN cdev stays registered with
si_drv1 -> m -> devinfo == freed sc:
- any later
open("/dev/mixerN")/ioctl(MIXER_*)dispatchescmimix_set/cmimix_init/cmimix_setrecsrc(cmi.c:654-750) βmix_getdevinfo(m)== freedscβcmi_rd/cmi_wr(sc, ...)reads and writes through the freed ~264-byte M_DEVBUF chunk (sc->st,sc->sh, i.e. type-confused bus_space tag/handle read from freed heap). - the mixer keeps its OWN
m->lock(mixer.c:71-72), so the UAF is not masked by the freedsc->lock.
Why not runtime-reproduced on this guest
- No hardware: QEMU presents no CMedia 13f6 device and no audio
class 0x040x function at all (guest_check.log / run.log:
pciconf -lshows only 1237/7000/7010/7113/1111:1234/virtio).cmi_probe()never matches βcmi_attach()never executes. - Module gate:
kldload snd_cmion this guest fails withKLD snd_cmi.ko: depends on midi - not available or version mismatch(serial console) β deployment itself is broken on the snapshot, so even a hypothetical device could not reach the driver. - Trigger gate (real hardware): the sole post-
mixer_initfailure path ispcm_register(), which only fails via pcm_veto_load (0 after sound(4) feeder init, feeder.c:126 β unreachable during PCI attach) or >255 sound units. Identical trigger shape to DF-4193/4334/4344/4421.
Ceiling on real hardware: local unprivileged UAF read/write through a 0666 cdev gated by an attach-time failure β Low, matching family precedent.
Negative results (classes hunted and killed in cmi.c, pass 2)
cmi_intr()NULL-channel (cmi.c:562-568):sc->pch.channelis set duringpcm_addchanβpcm_chn_createβchn_initβcmichan_init(sound.c:695-β¦, cmi.c:345-374) i.e. still within attach at cmi.c:994-995; before that, CH0/CH1 INTR_STATUS bits require DMA enabled whichcmi_init(cmi.c:874-878) disables. Family-wide unguardedchn_intr(sc->pch.channel)(es137x.c:750, via8233.c:833, atiixp.c:712) β no cmi differential. Killed.pcm_addchanreturn ignored (cmi.c:994-995): robustness only β channel-less device, no chn_intr(NULL) reachable (DMA never enabled).cmichan_getptrmodulo (cmi.c:529-531):sndbuf_resizefloor is blkcnt>=2 Γ blksz>=16 (buffer.c sndbuf_resize EINVAL guard) sosndbuf_getsize()>= 32 β no div-by-zero; physptr arithmetic wraps within u32 and stays bounded by% sz.cmi_dma_progu16 sample counts (cmi.c:261-267): sz β€ 65536 (pcm_getbuffersize max 65536, cmi.c:969; dma tag maxsize sc->bufsz, cmi.c:971-977), bps β {1,2,4} β s,i β€ 65535, no truncation; i>=0 because sz β₯ 2*bps.cmimix_setcmt[dev] OOB (cmi.c:684):devbounded by SOUND_MIXER_NRDEVICES in mixer.c before MIXER_SET dispatch (mixer_set validatesmix->devs & (1<<dev)), and cmi's ownmix_setdevsonly enables cmt[i].bits != 0 entries.cmpci_rate_to_regvalue(cmi.c:222-247): i β€ 7, cmi_rates[7] valid.cmi_resumelock recursion (cmi.c:1067-1092):snd_mtxcreateuses LK_CANRECURSE (sound.c) β safe.cmi_detach(cmi.c:1021-1047):pcm_unregisterfirst (drains channels+cdevs), then teardown;sc->mpu_regNULL-guarded; mpu401 paths dead (if (0)at cmi.c:957-958) β cmi_mread/cmi_mwrite hardcoded port 0x330 with NULL bus_space tags would be broken if ever enabled, but unreachable (note only).- DMA tag: nsegments=1, maxsize=sc->bufsz β€ 64K consistent with CMPCI DMA limits (cmi.c:969-977). No seg-count mismatch.
- SPDIF: no ioctl surface in cmi.c β sysctl
spdif_enabledonly (cmi_initsys, cmi.c:754-769);cmi_spdif_speedregister-only.
Fix validation
fix.diff reorders pcm_register() ahead of mixer_init() and adds
pcm_unregister(dev) on the mixer_init-failure path (pcm_unregister
itself calls mixer_uninit, sound.c:1197ff, and handles zero channels β
CHN_FOREACH over empty list). Validated to COMPILE cleanly with
-Werror on the guest toolchain:
- baseline snd_cmi.ko: RC=0 (build.log)
- patched snd_cmi.ko (fix applied): RC=0, -Werror clean (build.log, guest_fixbuild output preserved in build.log)
Behavioral baseline/patched runs impossible on this guest (no CMI
hardware β see above), hence fix_status=not_testable,
compile-validated. git apply --check passes against the pristine
read-only tree. Guest /usr/src restored to pristine after the build
(md5 3a977e0dea384942133aa5707146f390 matches tree).
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 (git apply --check OK against pristine tree) reorders pcm_register() before mixer_init() and adds pcm_unregister(dev) when mixer_init() fails (pcm_unregister calls mixer_uninit itself and handles the zero-channel case, sound.c:1146-1215). Compile-validated on the guest: patched snd_cmi.ko builds -Werror clean, RC=0 (build.log). Behavioral baseline/patched runs impossible on this guest: cmi_attach requires CMedia hardware the guest cannot present (fix_status not_testable, compile-validated).
fix.diff; build.log (patched module full compile+link, CMI_BUILD_RC=0); VERDICT.md 'Fix validation' section
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md root-cause walk (cmi.c:986-1016, mixer.c:687-764, sound.c:1016-1040); run.log + guest_check.log (no CMedia/audio PCI device, snd_cmi unloadable on snapshot, no /dev/mixer*); build.log (baseline AND fix.diff-patched snd_cmi.ko build -Werror clean, RC=0); fix.diff (git apply --check OK)
PoC changes
no seed; family trigger shape (DF-4334/4344/4421) adapted to cmi's single post-mixer failure path; hardware absent so guest gate-check substituted
Verified recommended fix
Move pcm_register() ahead of mixer_init() and pcm_unregister() on the mixer_init failure path so no live cdev remains over freed memory (DF-4334/4421 family shape)
Verdict
Code defect certain, kernel runtime hardware-gated: cmi_attach() calls mixer_init(dev, &cmi_mixer_class, sc) (cmi.c:986), which publishes a 0666 /dev/mixerN whose si_drv1 -> snd_mixer m has m->devinfo = sc (mixer.c:730-732, mixer.c:78), BEFORE pcm_register(dev, sc, 1, 1) (cmi.c:989). pcm_register can fail (EINVAL via pcm_veto_load sound.c:1021; ENODEV via unit>PCMMAXUNIT=255 sound.c:1027), and the bad: unwind (cmi.c:1004-1016) then destroys the dma tag, tears down the irq, releases resources, snd_mtxfree(sc->lock) and kfree(sc, M_DEVBUF) WITHOUT mixer_uninit(dev) -- the mixer cdev stays registered over freed memory and any unprivileged open/ioctl dispatches cmimix_* through mix_getdevinfo(m) == freed sc (cmi_rd/cmi_wr with a type-confused bus_space tag/handle read out of the freed ~264-byte chunk; the mixer's own m->lock survives, so nothing masks the UAF). Identical construct to the verified family DF-4156/4193/4299/4334/4344/4421; this is the 7th instance with the narrowest window (exactly one post-mixer goto: pcm_register). Not runtime-reproducible on the audit guest: QEMU presents no CMedia 13f6 / class-040x PCI function (guest_check.log), and this snapshot's snd_cmi.ko itself fails kldload with 'depends on midi - not available or version mismatch' (serial console, run.log), so cmi_attach never executes. Ceiling on real hardware: local unprivileged UAF read/write through a 0666 cdev, gated by an attach-time failure -- Low, matching family precedent.
No comments yet.