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

via_attach() pcm_register()-failure path destroys the AC97 codec while the 0666 /dev/mixerN mixer_init() created survives β€” dangling mixer devinfo UAF on open/ioctl

Summary

via_attach ignores mixer_init's return, which make_dev's a world-writable 0666 /dev/mixerN whose si_drv1->devinfo is the just-created AC97 codec. It then retains a failure point: pcm_register returns EINVAL (veto_load stuck) or ENODEV (>255 units). On failure, bad: runs ac97_destroy (kobj_delete + kfree) while /dev/mixerN still dispatches ioctls into it - UAF with indirect-dispatch potential; no mixer_uninit ever runs because attach failed. The FOURTH instance of the attach-error mixer family (DF-4156/4193/4299). Boot-gated precondition. Reorder-pcm_register-first + check-mixer_init fix (git-apply clean).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4334 Β· 11 files
FileTypeDescriptionSize
README.md β€” 1.4 KB ↓ raw
VERDICT.md β€” 4.1 KB ↓ raw
via8233_probe.c β€” 2.4 KB view raw
build.sh β€” 111 B view raw
run.sh β€” 145 B view raw
build.log β€” 281 B view raw
run.log β€” 281 B view raw
env.txt β€” 696 B view raw
fix.diff β€” 995 B view raw
manifest.json β€” 1.3 KB view raw
verdict.json β€” 3.6 KB view raw

DF-4334 β€” via_attach() late-failure path frees the AC97 codec while mixer_init()'s 0666 /dev/mixerN survives (mixer UAF)

File: sys/dev/sound/pci/via8233.c Β· pass 2 (GLM 5.3) Β· family: attach-error-path (DF-4156 maestro, DF-4193 es137x, DF-4299 emu10kx-pcm)

Build

# on the audit guest (root):
cc -O -Wall -o /root/via8233_probe /root/via8233_probe.c

Run

./dfbsd-qemu/vm.sh run_root '/root/via8233_probe'
pciconf -l | grep 1106:3059        # -> nothing: no VIA audio function
ls /dev/mixer*                     # -> nothing: no pcm unit exists

Expected

The probe cannot and does not trigger the UAF: the defect fires only when pcm_register() fails after mixer_init() succeeded during snd_via8233 attach. On this guest no 1106:3059 PCI function exists (host QEMU 7.2.22 predates the via-ac97 emulation added in QEMU 8.2), and the pcm_register() failure preconditions (pcm_veto_load stuck at 1 β€” cleared only by successful feeder init at boot; or pcm unit > 255) are boot-time anomalies an unprivileged user cannot create. The probe records that negative environment; the finding is source-certain (via8233.c:1335 vs via8233.c:1354-1373).

Fix

fix.diff β€” reorder pcm_register() before AC97_CREATE/mixer_init() and check mixer_init()'s return, eliminating every failure path after the mixer is published (mirrors FreeBSD upstream, where pcm_register() became void and the late-failure path no longer exists).

VERDICT.md
↓ download raw

VERDICT β€” DF-4334 (via8233.c attach-path mixer UAF)

Status: untested (attach-failure-gated, source-certain). Severity Low. Confidence certain.

The bug (source chain, every step cited)

  1. via_attach() initializes hardware, then creates the codec: via->codec = AC97_CREATE(dev, via, via_ac97) β€” via8233.c:1331 (ac97.c:1012-1030: codec->devinfo = via, kobj methods allocated).
  2. mixer_init(dev, ac97_getmixerclass(), via->codec) β€” via8233.c:1335 β€” its return value is ignored. Inside mixer_init: m = mixer_obj_create(dev, cls, devinfo=codec, ...) stores the codec as m->devinfo (mixer.c:635 area) and make_dev(&mixer_ops, ..., 0666, "mixer%d") publishes the world-writable node with pdev->si_drv1 = m (mixer.c:730-733). snddev->mixer_dev = pdev.
  3. The next failure point is pcm_register(dev, via, ..., NWRCHANS) β€” via8233.c:1354-1355 β€” which returns non-zero when pcm_veto_load != 0 (sound.c:1021-1025; the variable is initialized to 1 at sound.c:50 and cleared only by successful feeder registration at boot, feeder.c:120-126) or when device_get_unit(dev) > PCMMAXUNIT (=255, sound.c:1027-1034, sound.h:107).
  4. goto bad unwinds with ac97_destroy(via->codec) β€” via8233.c:1372-1373 β€” which kobj_delete(codec->methods)s the dispatch table, frees the codec lock and kfree(codec, M_AC97) (ac97.c:852-858). No mixer_uninit() runs anywhere on this path (device attach failed, so via_detach / pcm_unregister are never called; sound.c:1205 shows the canonical unwind does mixer_uninit first).
  5. Result: the 0666 /dev/mixerN remains registered forever with si_drv1 pointing at the freed snd_mixer β†’ freed devinfo codec β†’ freed kobj method table. Any later open/ioctl on that node (e.g. SOUND_MIXER_READ_DEVS β†’ mixer_ioctl β†’ MIXER_GETDEV(m->devinfo)) is a use-after-free with indirect-dispatch potential through kobj_deleted memory.

This is the same defect family the audit already verified by source in maestro (DF-4156), es137x (DF-4193) and emu10kx-pcm (DF-4299); via8233.c is a fourth instance that pass 1 missed. FreeBSD upstream (current head) sidesteps the class by making pcm_register() void β€” its file has no post-mixer_init failure path at all; DragonFly kept the check, keeping the bug reachable.

Why untested (not false-positive)

  • The trigger needs an actual VIA 1106:3059 audio function whose attach runs to mixer_init and then fails at pcm_register. The audit guest has no 1106:3059 function: host QEMU is 7.2.22 (-device help lists only AC97/ES1370/intel-hda) and the via-ac97 emulation (hw/audio/via-ac97.c, rev 0x50 β†’ probe-match "VIA VT8235", config 0x40=1 codec-ready) only exists from QEMU 8.2.
  • Even with the hardware, both pcm_register() failure preconditions are boot-time anomalies (pcm_veto_load feeder-init failure / >255 pcm units) that an unprivileged user cannot create β€” same verdict logic as DF-4156. Hence Low severity, impact: none demonstrated, confidence: certain in the source chain.

PoC artifacts

  • via8233_probe.c + build.log/run.log: guest-side proof of the negative environment (no mixer node, no 1106:3059, module absent).
  • env.txt: guest uname, host QEMU version/device list.

Fix

fix.diff (git-apply-able against the read-only sys/ tree; never applied there): move pcm_register() before AC97_CREATE/mixer_init() and fail attach on mixer_init() error. After the reorder, no failure path exists after the mixer is published, so the bad: unwind stays symmetric β€” no mixer_uninit bookkeeping needed. Not validated on the guest for the same hardware-absence reasons (fix_status: not_testable); git apply --check clean against sys/dev/sound/pci/via8233.c.

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.diff reorders pcm_register() ahead of codec creation/mixer_init and checks mixer_init(), removing every failure path after the mixer is published; git apply --check is clean. Baseline vs patched behaviour is not observable on the audit guest for the same hardware/precondition absence (no 1106:3059 function, pcm_register failure not creatable).

findings/poc/DF-4334/fix.diff; canonical unwind order shown at sound.c:1205 (pcm_unregister does mixer_uninit before codec teardown)
↓ fix.diffper-fix-DF-4334

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-4334/VERDICT.md (full source chain); via8233.c:1335 mixer_init ignored vs via8233.c:1354 pcm_register check vs via8233.c:1372 bad: ac97_destroy; mixer.c:730 make_dev 0666 + si_drv1=m(devinfo=codec); ac97.c:852 kobj_delete+kfree; sound.c:1021/1027 failure set; build.log/run.log (guest-side negative-environment probe); env.txt (QEMU 7.2.22 device list, no via-ac97)

PoC changes

authored fresh probe (no seed); probe documents the absent trigger environment rather than the UAF itself

Verified recommended fix

Move pcm_register() before AC97_CREATE/mixer_init() and fail attach on mixer_init() error so no failure path exists after the mixer is published

Verdict

via_attach() ignores mixer_init()'s return at via8233.c:1335 (which publishes a 0666 /dev/mixerN whose si_drv1->devinfo is the AC97 codec, mixer.c:730-733) and keeps a failure path after it: pcm_register() at via8233.c:1354 returns EINVAL/ENODEV when pcm_veto_load is stuck at its sound.c:50 initial value 1 (cleared only by successful boot-time feeder init, feeder.c:126) or when the pcm unit exceeds PCMMAXUNIT=255 (sound.c:1027). On that failure attach jumps to bad: and ac97_destroy(via->codec) at via8233.c:1372-1373 kobj_delete()s the method table and kfrees the codec (ac97.c:852-858) while /dev/mixerN still dispatches ioctls into it -- use-after-free with indirect-dispatch potential, identical to the DF-4156/DF-4193/DF-4299 family. Untested rather than demonstrated: the trigger needs a real VIA 1106:3059 audio function whose attach reaches mixer_init and then fails at pcm_register; the audit guest has no such function (host QEMU 7.2.22 predates the via-ac97 emulation added in QEMU 8.2 -- see env.txt) and both pcm_register failure preconditions are boot-time anomalies an unprivileged user cannot create.