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

cs4281 attach ignores mixer_init()/pcm_addchan() failures; half-attached device NULL-derefs in cs4281_pci_resume (ch->parent NULL and mixer_dev NULL)

Summary

Three returns ignored; attach reports success via pcm_setstatus. If chan_init's sndbuf_alloc fails, the channels keep parent==NULL; if mixer_init failed, mixer_dev stays NULL - a later suspend/resume panics twice over (cs4281_rd with sc==NULL; mixer_reinit on the NULL devt). Root-gated DoS (DMA pressure + resume). Check-returns + resume-backstop fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4539 Β· 12 files
FileTypeDescriptionSize
README.md β€” 2.1 KB ↓ raw
VERDICT.md β€” 2.5 KB ↓ raw
cs4281_probe.c β€” 2.0 KB view raw
build.sh β€” 133 B view raw
run.sh β€” 138 B view raw
build.log β€” 94 B view raw
run.log β€” 428 B view raw
env.txt β€” 604 B view raw
wedge_episode.txt β€” 777 B view raw
fix.diff β€” 504 B view raw
manifest.json β€” 1016 B view raw
verdict.json β€” 3.4 KB view raw

DF-4539 β€” cs4281 attach ignores mixer_init()/pcm_addchan() failures; half-attached device NULL-derefs on suspend/resume

What this is

cs4281_pci_attach() ignores three return values and advertises success unconditionally:

  • mixer_init(dev, ...) at cs4281.c:845 β€” return ignored. If it fails, snddev->mixer_dev stays NULL, yet attach succeeds.
  • pcm_addchan(dev, PCMDIR_PLAY, ...) (cs4281.c:850) and pcm_addchan(dev, PCMDIR_REC, ...) (cs4281.c:851) β€” returns ignored. pcm_addchan fails (sound.c:1125-1141) when chn_init β†’ cs4281chan_init fails, which happens when sndbuf_alloc() fails (cs4281.c:326-328). That early return leaves the embedded sc->pch/sc->rch with ch->parent == NULL, ch->channel == NULL (set only at cs4281.c:329-330/337, after the failure return).

The driver then calls pcm_setstatus and reports the device healthy.

Consequences (both root-gated: ACPI suspend/resume)

  • cs4281_pci_resume() (cs4281.c:941-949) unconditionally invokes cs4281chan_setspeed(NULL, &sc->rch, ...) etc. β†’ adcdac_go(ch, 0) (cs4281.c:474-479) β†’ cs4281_rd(sc, ...) with sc = ch->parent == NULL β†’ NULL-pointer dereference panic.
  • cs4281_pci_resume β†’ mixer_reinit(dev) (cs4281.c:935) β†’ mixer.c:828-829: pdev = mixer_get_devt(dev) returns snddev->mixer_dev (NULL after the ignored mixer_init failure, mixer.c:118-124) and is derefenced pdev->si_drv1 without a check β†’ NULL-pointer dereference panic.

No unprivileged trigger: suspend/resume requires root. Severity Low (robustness/DoS under resource pressure at attach + later suspend).

Build / run

./build.sh    # guest build of the environment probe
./run.sh      # documents: no CS4281 hardware on this guest

Fix

fix.diff adds defensive guards to cs4281_pci_resume (skip restore when either channel never initialized). The attach-side checks for mixer_init/pcm_addchan returns + bad_unregister: unwind are in the DF-4537 fix (same attach function); this diff is the resume-side backstop. git apply --check clean; never applied to sys/.

VERDICT.md
↓ download raw

DF-4539 VERDICT β€” untested (hardware-gated), analysis certain

Source chain

  1. mixer_init() return ignored: cs4281.c:845.
  2. pcm_addchan() returns ignored: cs4281.c:850-851. pcm_addchan fails when chn_init fails (sound.c:1125-1141); cs4281chan_init fails when sndbuf_alloc fails (cs4281.c:326-328) and returns BEFORE ch->parent = sc; ch->channel = c; ch->dma_chan = ... (cs4281.c:329-337), so the embedded channel structs keep parent == NULL from the M_ZERO'd softc (cs4281.c:765).
  3. Despite both failures the attach path continues to pcm_setstatus (cs4281.c:856) and returns 0.
  4. On a later suspend/resume: * cs4281_pci_resume (cs4281.c:941-949) calls cs4281chan_setspeed(NULL, &sc->rch, sc->rch.spd) β†’ adcdac_go(ch, 0) (cs4281.c:474-479) β†’ cs4281_rd(sc, ...) (cs4281.c:146-150) with sc = ch->parent == NULL β†’ NULL deref (reads sc->st at offset of bus_space tag) β†’ panic. * mixer_reinit(dev) at cs4281.c:935 β†’ mixer.c:828-829 pdev = mixer_get_devt(dev); m = pdev->si_drv1; where mixer_get_devt (mixer.c:118-124) returns snddev->mixer_dev == NULL when the ignored mixer_init failed β†’ NULL deref panic.

Preconditions

  • sndbuf_alloc failure (busdma/DMA memory pressure at attach) for the addchan leg, or any mixer_init failure for the mixer leg;
  • then ACPI suspend+resume (root).

No unprivileged path exists; both legs need root to trigger. Low.

Why untested

The audit guest has no vendor-1013 CS4281 function (env.txt) β€” the attach, hence both failure legs, cannot occur; suspend of a nonexistent device is equally unreachable. Analysis is line-verified; classification untested, confidence certain.

Fix validation

fix.diff guards cs4281_pci_resume to fail cleanly when either channel never initialized (sc->rch.parent == NULL || sc->pch.parent == NULL β†’ ENXIO before mixer_reinit/channel restore). The attach-side half (check mixer_init/pcm_addchan, unwind via pcm_unregister) ships in the DF-4537 fix; both apply clean (git apply --check). Not observable on this guest β†’ fix_status not_testable.

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 adds the resume-side guards (fail ENXIO before mixer_reinit/channel restore when sc->rch.parent or sc->pch.parent is NULL); git apply --check clean. Combined with the DF-4537 attach reorder, no half-attached device can reach resume. Not observable on this guest (no CS4281 hardware).

findings/poc/DF-4539/fix.diff; attach-side half in findings/poc/DF-4537/fix.diff
↓ fix.diffper-fix-DF-4539

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-4539/VERDICT.md; cs4281.c:845 ignored mixer_init, cs4281.c:850-851 ignored pcm_addchan, cs4281.c:326-337 early-return-before-parent-init, cs4281.c:941-949 unconditional resume restore, cs4281.c:474-479 adcdac_go parent deref, mixer.c:828-829 mixer_reinit NULL pdev, sound.c:1125-1141 pcm_addchan failure semantics; run.log/env.txt

PoC changes

no seed; environment probe shared with DF-4537 (same trigger-hardware absence)

Verified recommended fix

Check mixer_init() and pcm_addchan() returns and unwind via pcm_unregister on failure (DF-4537 fix.diff), plus guard cs4281_pci_resume to return ENXIO when either embedded channel never initialized

Verdict

cs4281_pci_attach() ignores the return of mixer_init() (cs4281.c:845) and of both pcm_addchan() calls (cs4281.c:850-851), then reports the device healthy via pcm_setstatus (cs4281.c:856). If sndbuf_alloc() fails inside cs4281chan_init (cs4281.c:326-328) the embedded sc->pch/sc->rch keep parent==NULL (set only later at cs4281.c:329-337), and if mixer_init() fails snddev->mixer_dev stays NULL. A later suspend/resume then NULL-derefs twice: cs4281_pci_resume (cs4281.c:941-949) calls cs4281chan_setspeed -> adcdac_go (cs4281.c:474-479) -> cs4281_rd with sc==ch->parent==NULL (cs4281.c:146-150), and mixer_reinit (cs4281.c:935) derefs mixer_get_devt()'s NULL return at mixer.c:828-829 (snddev->mixer_dev per mixer.c:118-124). Guaranteed panic when the half-attach condition holds; root-gated (ACPI suspend), plus resource-pressure precondition, so Low. Untested: needs a CS4281 PCI function whose attach half-fails then a suspend cycle; the audit guest has no vendor-1013 function (env.txt) so neither the failure legs nor suspend of this device can occur.