via_attach(): five failure paths after mixer_init() free the AC97 codec while the 0666 /dev/mixerN survives β dangling mixer devinfo UAF on unprivileged open/ioctl (DF-4156 mixer-UAF family, 16th instance)
Summary
mixer_init publishes the world-writable cdev before five fallible steps remain (2x dma-tag, dmamem_alloc, dmamap_load, pcm_register); the bad: unwind destroys the codec and frees the softc without mixer_uninit - DF newbus never detaches a failed attach, so the cdev permanently dangles and any unpriv mixer ioctl executes codec ops over freed codec+lock+softc. 16th family instance; the load-bearing premise was dynamically verified on this guest by DF-4724's KLD. bad_mixer+goto fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4743 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.7 KB | β raw | |
| VERDICT.md | β | 5.3 KB | β raw | |
| run.log | β | 2.2 KB | view raw | |
| env.txt | β | 452 B | view raw | |
| fix.diff | β | 1.9 KB | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| verdict.json | β | 3.1 KB | view raw | |
| DF-4116_live_repro/panic.txt | β | 640 B | view raw | |
| DF-4116_live_repro/NOTES.txt | β | 2.2 KB | view raw |
DF-4743 β via82c686: attach-failure paths after mixer_init() leave the live 0666 /dev/mixerN over the freed codec β mixer-UAF family, 16th instance (DF-4156/4193/4299/4334/4344/4421/4457/4468/4504/4537/4550/4552/4610/4651/4724)
Where
via_attach() publishes the mixer at line 548:
via->codec = AC97_CREATE(dev, via, via_ac97); /* via82c686.c:544 */
if (!via->codec)
goto bad;
if (mixer_init(dev, ac97_getmixerclass(), via->codec)) /* :548 -> /dev/mixerN */
goto bad;
mixer_init() does make_dev(&mixer_ops, ..., 0666, "mixer%d", unit) and
stores the codec as the mixer's devinfo (pdev->si_drv1 = m,
m->devinfo = devinfo β sys/dev/sound/pcm/mixer.c:709,730-733,
mixer.c:635). After that point via_attach() keeps five failure paths:
bus_dma_tag_create(parent_dmat)β via82c686.c:556-565bus_dma_tag_create(sgd_dmat)β via82c686.c:572-582bus_dmamem_alloc(sgd_table)β via82c686.c:584-586bus_dmamap_load(sgd_dmamap)β via82c686.c:587-589pcm_register(dev, via, 1, 1)β via82c686.c:596
The bad: unwind then runs
if (via->codec) ac97_destroy(via->codec); /* :602 β kobj_delete + kfree(codec) (ac97.c:852-858) */
...
if (via) kfree(via, M_DEVBUF); /* :611 */
with no mixer_uninit(dev) anywhere. DragonFly newbus never detaches a
failed attach, so the world-writable /dev/mixerN cdev stays registered,
its si_drv1 -> m -> devinfo pointing at the freed codec, and through
via_ac97 methods at the freed via (bus_space handle, lock β the lock
itself was snd_mtxfreed at :610). Any unprivileged user opening the node
and issuing a mixer ioctl reaches via_read_codec/via_write_codec
(via82c686.c:180-209) over freed memory β UAF read/write.
Family premise (failed attach leaves live 0666 cdev reachable by
unprivileged open) was already dynamically verified on this same guest
kernel by the DF-4724 evidence run (make_dev-then-fail KLD:
/dev/premtest0 persisted 0666 after "attach returned 6"; uid=1001
open() returned 0 and reached module code β findings/poc/DF-4724/).
Guest reachability facts (live, this run β see run.log)
snd_via82c686.koships in/boot/kernel(unlike aureal, DF-4724, which was unwired):sys/conf/files:2156andsys/dev/sound/driver/Makefile:5both wire it.kldload snd_via82c686succeeds on the audit guest; with no VIA 686A on the PCI bus nothing attaches, nomixer0unit is created β the UAF trigger requires the physical card + one attach failure after line 548.
Fix
Call mixer_uninit(dev) on every failure after mixer_init() succeeds
(see fix.diff) β same idiom as the family fix in DF-4724.
DF-4743 VERDICT β via82c686 mixer-UAF family, 16th instance
Status: untested (end-to-end), reproduced: 0, confidence: certain β same treatment as the 15 filed family instances (DF-4156, 4193, 4299, 4334, 4344, 4421, 4457, 4468, 4504, 4537, 4550, 4552, 4610, 4651, 4724), all of which are hardware-gated.
Static chain (line-verified)
via_attach()creates the codec (via82c686.c:544) and publishes the mixer at :548 βmixer_init()make_dev(0666 "/dev/mixerN") +pdev->si_drv1 = mwithm->devinfo = via->codec(mixer.c:709,730-733; m->devinfo set in mixer_obj_create mixer.c:635).- Five failure paths remain after it: parent_dmat tag (:556-565), sgd_dmat tag (:572-582), bus_dmamem_alloc (:584-586), bus_dmamap_load (:587-589), pcm_register (:596). pcm_register can fail e.g. via the pcm veto flag; the dma paths on allocation failure.
bad:unwind callsac97_destroy(via->codec)(:602 β kobj_delete + kfree(codec), ac97.c:852-858),snd_mtxfree(via->lock)(:610) andkfree(via)(:611) β no mixer_uninit / destroy_dev anywhere.- DragonFly newbus never detaches a failed attach (subr_bus.c β DF-4724's verified citation), so /dev/mixerN stays live, world-writable.
- Any unprivileged user: open /dev/mixerN β mixer_open/mixer_ioctl β
mix_getdevinfo(m) β freed codec kobj β
via_read_codec/via_write_codec(via82c686.c:180-209) on freedvia(wild bus_space handle, freed mutex) β UAF read/write in kernel context. Window: permanent (until reboot) once triggered.
Live evidence gathered on the audit guest (run.log)
snd_via82c686.koships in /boot/kernel and is wired into the build (sys/conf/files:2156, sys/dev/sound/driver/Makefile:5) β unlike the aureal sibling (DF-4724) this driver is onekldloadaway whenever a card is present.kldload snd_via82c686on the guest: loads cleanly; no VIA 0x30581106 on the QEMU PCI bus β no attach, so no failure path executes.- The load-bearing family premise (failed attach leaves a live 0666 cdev reachable by unprivileged open; newbus never detaches failed attaches) was dynamically verified on this same 6.5-DEVELOPMENT guest kernel by the DF-4724 run β it is newbus/mixer.c behavior, driver-independent.
Not reproducible here
The UAF requires the physical VIA VT82C686A southbridge audio
(PCI 1106:3058) AND an attach failure after line 548. QEMU does not
emulate it; pciconf -l on the guest confirms absence. Hence untested,
matching every family instance's verdict.
Bonus: DF-4116 live-reproduced during this run
While premise-testing, kldload snd_via82c686; kldunload snd_via82c686
panicked the guest in sndstat_sysuninit β a controlled A/B reproduction
(sndstat read β unload OK; reload β unload panics) of the already-filed
DF-4116. See DF-4116_live_repro/ (panic.txt + NOTES.txt). Not
re-filed as a new finding; orchestrator should attach the evidence to
DF-4116 (recommend status=reproduced, impact=panic).
Negative checks on via82c686.c (pass-2 classes considered and killed)
- via_buildsgdt unbounded segs (via82c686.c:232-240, writes
sndbuf_getsize()/ch->blkszentries into a 4-entry/32-byte table, ignores sndbuf_resize result at :336): KILLED β every value reachingviachan_setblocksizecomes from chn_resizebuf, whoseround_blksz()floors at 16 (channel.c:1591-1609,return retafter the grow-loop) and whose maxsize clamps (channel.c:1839-1848) guarantee 2*hblksz β€ maxsize β sndbuf_resize(2,hblksz) always succeeds β segs == SEGS_PER_CHAN == 2. via8233's clamping (via8233.c:565-596,ch->blksz = sndbuf_getblksz()) is defensive, not a bug marker. div0 (blksz=0) also dead: chn_reset (channel.c:1190-1200) forces a resizebuf (β setblocksize) at every dsp open before any trigger can run. - viachan_getptr garbage base (:391-396): consumers clamp via
% sndbuf_getsize()(chn_dmaupdate channel.c:377-391) β bounded. - setformat/setspeed: no div0, codec_caps-gated VRA path only β fine.
- via_intr on half-attached device (bad: path leaves handler till :604 with modes pre-armed at :541-542): only fires if DMA-status INTR bits assert without started DMA; shared-IRQ reads of a reset codec clear bits β needs HW misbehavior, dropped.
- AC97 protocol waits (:144-176): 50ΓDELAY(1) polls, bounded β fine (contrast DF-4728's 0.35 s spins).
- bad: unwind order: codec destroyed while intr still armed β same dropped-class as above; resource release order otherwise sound (sgd_addr-guarded unload, NULL-guarded frees).
- pcm_addchan returns ignored (:597-598): worst case a registered device with 0 channels β open ENODEV; no memory unsafety, and no suspend/resume methods exist here (the DF-4539 resume-deref class doesn't apply).
- via_detach: gated on pcm_unregister success β correct.
Recommended fix
mixer_uninit(dev); via->codec = NULL; on every failure after
mixer_init() (fix.diff), family idiom from DF-4724.
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 not kernel-validated: the UAF cannot be triggered on this guest (no VT82C686A hardware). diff is line-exact against the verified bad: unwind.
fix.diff (bad_mixer label + mixer_uninit before the free path; ac97mix_uninit frees the codec so codec=NULL prevents the bad:-path double free)
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (module ships/loads, no hw on PCI bus), VERDICT.md (full static chain + killed-candidate list incl. the via_buildsgdt unbounded-segs analysis killed by round_blksz floor channel.c:1591-1609), DF-4116_live_repro/ (bonus live panic)
PoC changes
n/a (no seed; premise evidence gathered live)
Verified recommended fix
mixer_uninit(dev) + via->codec=NULL on every failure after mixer_init() succeeds (fix.diff; family idiom from DF-4724)
Verdict
16th instance of the DF-4156 mixer-UAF family: via_attach publishes the 0666 /dev/mixerN at mixer_init (via82c686.c:548; make_dev+si_drv1=m at mixer.c:730-733, m->devinfo=codec) and keeps five failure paths after it (bus_dma_tag_create x2 at 556-565/572-582, bus_dmamem_alloc at 584-586, bus_dmamap_load at 587-589, pcm_register at 596); the bad: unwind frees the codec (ac97_destroy at 602, kfree at ac97.c:858) and via (611) with no mixer_uninit, and DragonFly newbus never detaches a failed attach, so the cdev dangles into freed memory - any unprivileged mixer open/ioctl reaches via_read_codec/via_write_codec (via82c686.c:180-209) over freed codec+softc+lock. End-to-end not runnable in QEMU (no VIA 1106:3058 on pciconf -l, see run.log); the load-bearing family premise (failed attach leaves live 0666 node reachable by unpriv users) was dynamically verified on this same guest kernel by DF-4724's run. Driver IS wired into the build and ships in /boot/kernel (sys/conf/files:2156, sys/dev/sound/driver/Makefile:5) - unlike the aureal sibling - so only the card is missing.
No comments yet.