β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4743

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4743 Β· 9 files
FileTypeDescriptionSize
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-565
  • bus_dma_tag_create(sgd_dmat) β€” via82c686.c:572-582
  • bus_dmamem_alloc(sgd_table) β€” via82c686.c:584-586
  • bus_dmamap_load(sgd_dmamap) β€” via82c686.c:587-589
  • pcm_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.ko ships in /boot/kernel (unlike aureal, DF-4724, which was unwired): sys/conf/files:2156 and sys/dev/sound/driver/Makefile:5 both wire it.
  • kldload snd_via82c686 succeeds on the audit guest; with no VIA 686A on the PCI bus nothing attaches, no mixer0 unit 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.

VERDICT.md
↓ download raw

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)

  1. via_attach() creates the codec (via82c686.c:544) and publishes the mixer at :548 β€” mixer_init() make_dev(0666 "/dev/mixerN") + pdev->si_drv1 = m with m->devinfo = via->codec (mixer.c:709,730-733; m->devinfo set in mixer_obj_create mixer.c:635).
  2. 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.
  3. bad: unwind calls ac97_destroy(via->codec) (:602 β†’ kobj_delete + kfree(codec), ac97.c:852-858), snd_mtxfree(via->lock) (:610) and kfree(via) (:611) β€” no mixer_uninit / destroy_dev anywhere.
  4. DragonFly newbus never detaches a failed attach (subr_bus.c β€” DF-4724's verified citation), so /dev/mixerN stays live, world-writable.
  5. 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 freed via (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.ko ships 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 one kldload away whenever a card is present.
  • kldload snd_via82c686 on 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->blksz entries into a 4-entry/32-byte table, ignores sndbuf_resize result at :336): KILLED β€” every value reaching viachan_setblocksize comes from chn_resizebuf, whose round_blksz() floors at 16 (channel.c:1591-1609, return ret after 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.

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_testable
baseline no→ patch + rebuild →patched clean

Fix 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)
↓ fix.diffper-fix-DF-4743

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.