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

atiixp_chip_post_init mixer_init/pcm_register error-path asymmetry leaves a live 0666 /dev/mixerN over freed codec+softc β€” UAF on open/ioctl and NULL-deref suspend (DF-4156 family, 5th instance)

Summary

mixer_init publishes a world-writable /dev/mixerN pointing at the AC97 codec before pcm_register can still fail; the error path then ac97_destroys the codec and kfrees the softc while the mixer cdev and the still-attached device keep pointing at freed memory. Any later mixer open/ioctl dispatches through the kobj_delete'd methods (UAF); suspend runs on the devinfo pcm_register never stored (NULL-deref); detach leaks the cdev permanently. Attach-failure-gated (Low per family). mixer_uninit-first fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4344 Β· 7 files
FileTypeDescriptionSize
README.md β€” 2.9 KB ↓ raw
VERDICT.md β€” 7.1 KB ↓ raw
guest_check.log β€” 782 B view raw
panic_df4116_on_atiixp_unload.txt β€” 1.3 KB view raw
fix.diff β€” 1.1 KB view raw
manifest.json β€” 959 B view raw
verdict.json β€” 3.7 KB view raw

DF-4344 β€” atiixp_chip_post_init mixer/pcm_register error-path asymmetry (DF-4156 family, 5th instance)

What this is

sys/dev/sound/pci/atiixp.c orders device bring-up as:

  1. mixer_init(sc->dev, ac97_getmixerclass(), sc->codec) β€” atiixp.c:1085 β€” publishes a world-writable (0666) /dev/mixerN cdev whose si_drv1 devinfo chain points at the AC97 codec and, through the pcm softc, at sc (mixer.c:687-737).
  2. pcm_register(...) β€” atiixp.c:1087 β€” can still fail (pcm_veto_load EINVAL, sound.c:1021-1026; unit > PCMMAXUNIT ENODEV, sound.c:1028-1035).
  3. On failure: goto postinitbad β†’ atiixp_release_resource(sc) (atiixp.c:1113-1115) β†’ ac97_destroy(sc->codec) (atiixp.c:1129-1132, kobj_delete + kfree) and kfree(sc, M_DEVBUF) (atiixp.c:1165) β€” while the mixer cdev created in step 1 stays registered forever.

The device also remains attached (attach already returned 0 β€” post_init runs from a config_intrhook when cold, atiixp.c:1293-1300), so suspend/resume later run against the freed sc (pcm_getdevinfo returns the NULL/garbage devinfo left by the failed pcm_register; atiixp_lock(NULL) in atiixp_pci_suspend atiixp.c:1337 is a NULL-deref panic), and detach never tears the mixer down.

Same construct as the four verified family members: DF-4156 (maestro), DF-4193 (es137x), DF-4299 (emu10kx-pcm), DF-4334 (via8233). This file is the 5th instance, previously unfiled.

Reproduce (needs ATI IXP hardware)

Trigger: any machine with an ATI IXP 200/300/400/SB600 AC97 controller (PCI 1002:4341/4361/4370/4382) where pcm_register fails after mixer_init succeeded β€” e.g. boot with the sound feeder init failed (pcm_veto_load stuck β‰  0) or unit count above hw.snd.maxunit. Then, as any local user: open("/dev/mixer0") + SNDCTL_MIXER_* ioctl β†’ kernel operates on freed sc/codec (UAF; mixer dispatch through a kobj_deleted methods table).

Success criterion: use-after-free fault / adjacent-heap corruption in mixer_ioctl paths, or (suspend path) NULL-deref panic in atiixp_pci_suspend.

On this audit guest

Not runtime-testable: QEMU 7.2 does not emulate the ATI IXP AC97 controller. guest_check.log documents: module loads, probe returns ENXIO for every PCI device, no pcm device attaches, so the buggy path never executes. Source-level certainty only (identical construct to 4 verified family members).

panic_df4116_on_atiixp_unload.txt: incidental capture β€” unloading snd_atiixp on this guest panics in sndstat_sysuninit (known, already-filed DF-4116; not re-reported here). Kept as context: the module-unload route to exercise teardown is itself blocked by that bug.

Fix

fix.diff β€” tear the mixer down (which destroys both the cdev and, via ac97mix_uninit β†’ ac97_destroy, the codec) before atiixp_release_resource, and keep release_resource from destroying the codec a second time.

VERDICT.md
↓ download raw

DF-4344 VERDICT

Status: inconclusive (kernel runtime hardware-gated β€” no ATI IXP audio controller exists on the audit guest; defect certain at source level, the 5th verified instance of the DF-4156 family).

Root cause (code-proven)

atiixp_chip_post_init (atiixp.c:1001-1115):

1085:  mixer_init(sc->dev, ac97_getmixerclass(), sc->codec);   <-- publishes 0666 /dev/mixerN
1087:  if (pcm_register(sc->dev, sc, ATI_IXP_NPCHAN, ATI_IXP_NRCHAN))
1088:          goto postinitbad;                                <-- still-failing step
...
1113: postinitbad:
1114:  atiixp_release_resource(sc);                             <-- frees codec + sc

mixer_init (mixer.c:687-737) does make_dev(&mixer_ops, ..., 0666, "mixer%d", unit) and stores the mixer (whose devinfo is the codec) in pdev->si_drv1. Nothing in the failure path removes that cdev. atiixp_release_resource then does ac97_destroy(sc->codec) (atiixp.c:1129-1132; kobj_delete(codec->methods) + kfree(codec), ac97.c:852-859) and kfree(sc, M_DEVBUF) (atiixp.c:1165).

Result: any later open/ioctl on /dev/mixerN reaches mixer_ioctl β†’ mix_getdevinfo(m) β†’ freed codec, with dispatch through the deleted kobj methods table (indirect-call UAF), plus snddev->… state pointing at freed sc.

Secondary consequences of the same root: - The device stays attached (attach returned 0 at atiixp.c:1302 long before the delayed hook runs, and even the synchronous cold==0 branch returns 0). atiixp_pci_suspend (atiixp.c:1337) does atiixp_lock(sc) on the devinfo pcm_register never stored β†’ pcm_getdevinfo NULL β†’ snd_mtxlock(NULL) β†’ NULL-deref panic. - atiixp_pci_detach (atiixp.c:1315-1321) only unregisters when sc->codec != NULL; on this path it reads the stale softc pointer and either skips cleanup (mixer cdev + devnode leak, permanent UAF) or touches freed memory.

pcm_register failure modes on this path (sound.c:1016-1035): pcm_veto_load (EINVAL β€” feeder init failure at boot) or device_get_unit(dev) > PCMMAXUNIT (ENODEV). Both are operator/config controlled, not attacker-on-demand β€” hence Low severity, matching the family (DF-4156/4193/4299/4334 all Low).

Reproduction attempt on the audit guest

Guest: DragonFly 6.5-DEVELOPMENT X86_64_GENERIC (env below), QEMU 7.2 class emulator. pciconf -l: no vendor 0x1002 device of any kind β€” the ATI IXP 200/300/400/SB600 AC97 controllers (1002:4341/4361/4370/4382) are not emulated. Probe evidence (guest_check.log):

  • kldload snd_atiixp succeeds (registers the driver),
  • devinfo shows NO_PCM_DEVICE_ATTACHED β€” atiixp_pci_probe returns ENXIO for every PCI device, atiixp_pci_attach never runs, so the buggy postinitbad path is unreachable on this guest.

Therefore: kernel-runtime reproduction impossible here (hardware-gated), same classification the family got; the code defect itself is certain β€” byte-for-byte the same failure-ordering construct already verified in four sibling drivers.

Incidental capture (panic_df4116_on_atiixp_unload.txt): attempting to unload the module to at least exercise teardown panics in sndstat_sysuninit β€” that is known DF-4116 (already filed against sys/dev/sound/pcm/sndstat.c), not re-reported here.

Fix validation

fix.diff (git apply --check OK against the pristine tree): tear the mixer down before releasing everything it points at:

postinitbad:
    if (mixer_created && mixer_uninit(sc->dev) == 0)
        sc->codec = NULL;   /* mixer_uninit ac97_destroy'd it */
    atiixp_release_resource(sc);

mixer_uninit (mixer.c:767-822) does destroy_dev(pdev) and MIXER_UNINIT(m) β†’ ac97mix_uninit β†’ ac97_destroy(codec) (ac97.c:1030-1038) β€” i.e. it removes the dangling cdev AND the codec exactly once; NULLing sc->codec keeps atiixp_release_resource's ac97_destroy (atiixp.c:1129) from double-freeing. A kernel-build validation was not possible: building and booting an atiixp-attachable kernel requires the physical controller (probe gates attach), which the guest cannot emulate. fix_status: inconclusive (not hardware-demonstrable here), logic reviewed against the mixer.c/ac97.c teardown paths.

Negative results (pass-2 depth on atiixp.c, classes hunted & killed)

  • DMA descriptor ring geometry (atiixp.c:1272-1287, 474-478, 579-603): allocation is CHSEGS_MAX(256) * NCHANS(2) * 12 ops; atiixp_chan_init slices per channel at num*256 with registered_channels only ever reaching NCHANS=2 (pcm_addchan called exactly NPCHAN+NRCHAN=2 times, atiixp.c:1090-1093; vchans use their own class). buildsgdt writes < blkcnt ≀ 256 entries per slice β€” in bounds. next pointers stay inside the slice. u16 size field: blksz>>2 ≀ 16384 fits (polling mode single-descriptor case included). CLEAN.
  • Fragment/buffer math (atiixp.c:529-566): blksz aligned/ clamped to [32, maxsize/2], blkcnt to [2,256]; halving loop guarantees blkszblkcnt ≀ maxsize before sndbuf_resize, and sndbuf_resize (buffer.c:161-205) independently rejects blkszblkcnt>maxsize / blkcnt<2 / blksz<16 β€” no div0, no oversize descriptors. sc->blkcnt hint path (atiixp.c:1236-1252): a blocksize hint larger than bufsz makes blkcnt=0 β†’ 1 << (i-1) with i=0 is UB, but on x86 it yields 0x80000000 which the subsequent unsigned clamp maps to CHSEGS_MAX; root-only tunable; no memory-safety impact. KILLED.
  • pollticks division (atiixp.c:741-743, 783-785): divisor is align*spd; speed is pinned to 48000 by atiixp_chan_setspeed (atiixp.c:522-527) and align ∈ {1,2,4,8} post-format β€” the pcm core negotiates format/speed before any trigger. Not reachable with 0. KILLED.
  • Codec ready waits (atiixp.c:360-405): all bounded (500 Γ— DELAY(1) in waitready, 500 in rdcd's data phase) β€” bounded, under lock, fine. CLEAN.
  • SPDIF paths: no spdif channel/parse code exists in this driver; only register-bit mentions (atiixp.c:235-236, 813-814, 947). N/A.
  • Suspend/resume (atiixp.c:1330-1404): atiixp_pci_suspend computes value |= POWERDOWN|AC_RESET but writes only ATI_REG_CMD_POWERDOWN (atiixp.c:1354-1355) β€” cosmetic logic wart (AC_RESET never written), no security impact. The NULL-scuttled suspend entry is part of THIS finding's root. mixer_reinit failure returns ENXIO leaving interrupts disabled β€” availability-only, same attach-failure precondition. NOTED, not filed.
  • detach leak symmetry (atiixp.c:1310-1328): consistent with family; pcm_unregister (sound.c:1205) performs mixer_uninit internally, so the normal detach path does not double-free. CLEAN.

Bottom line

Certain, Low (attach-failure-gated, not user-triggerable on demand), 5th instance of the DF-4156 family in sys/dev/sound/pci/. Runtime proof requires IXP hardware the guest cannot emulate; defect and fix are code-certain by symmetry with the four verified siblings.

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

inconclusive
baseline no→ patch + rebuild →patched clean

fix.diff (git apply --check OK) tears the mixer down before release; mixer_uninit semantics verified against mixer.c:767-822 (destroy_dev + MIXER_UNINIT -> ac97_destroy). A patched-kernel run is impossible on this guest: atiixp_pci_attach requires the IXP controller the guest cannot emulate, so baseline/patched kernel behavior is not demonstrable here.

fix.diff; VERDICT.md 'Fix validation' section citing mixer.c:767-822 and ac97.c:1030-1038
↓ fix.diffper-fix-DF-4344

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md root-cause table (atiixp.c:1085-1115, mixer.c:687-737, ac97.c:852-859); guest_check.log (no ATI hardware, no attach); panic_df4116_on_atiixp_unload.txt (known DF-4116); fix.diff (mixer_uninit teardown, git apply --check OK)

PoC changes

no seed; family PoC shape (DF-4156/4334) adapted: trigger requires IXP hardware, guest probe substituted

Verified recommended fix

On the postinitbad path call mixer_uninit(sc->dev) (destroys /dev/mixerN and, via ac97mix_uninit, the codec) before atiixp_release_resource, and NULL sc->codec to prevent the double ac97_destroy

Verdict

Code defect certain, kernel runtime hardware-gated: atiixp_chip_post_init() orders mixer_init() (atiixp.c:1085, publishing a 0666 /dev/mixerN whose si_drv1 devinfo is the AC97 codec) before pcm_register() (atiixp.c:1087, which can still fail EINVAL/ENODEV via pcm_veto_load or PCMMAXUNIT, sound.c:1021-1035); the postinitbad path then frees the codec (ac97_destroy, atiixp.c:1129-1132) and sc (atiixp.c:1165) while leaving the mixer cdev and the still-attached device pointing at freed memory -- any later mixer open/ioctl is a UAF through a kobj_delete'd methods table, and suspend/resume NULL-deref through the devinfo pcm_register never stored. Identical construct to the four verified family members (DF-4156 maestro, DF-4193 es137x, DF-4299 emu10kx-pcm, DF-4334 via8233); this is the 5th instance. Not runtime-reproducible on the audit guest: QEMU has no ATI IXP AC97 controller (1002:4341/4361/4370/4382 absent, guest_check.log: kldload snd_atiixp loads but no pcm device attaches -- probe returns ENXIO). Incidental: kldunload of the module on the guest panics in sndstat_sysuninit, which is KNOWN DF-4116 (not re-reported).