Skip to content

ASIO driver use-after-free when a device fails the capability check #3868

Description

@ann0see

Summary

Use-after-free on the ASIO SDK's global theAsioDriver pointer when a device
fails the capability check.
On current main, a device that fails
CheckDeviceCapabilities() during ASIO re-init can leave a dangling global
pointer that a later re-init dereferences. Symptoms are inconsistent — hang,
crash, or silent corruption — which matches the reports in #872 and #305.

Context

Evidence (mcfnord, on Windows — issue #3779 comment 5224437188)

One honest caveat up front: this measurement was done with a synthetic test
driver
that deliberately fails the capability check, not with ASIO4ALL on real
hardware. With that said:

  • This is a use-after-free, not a leak: ASIOExit() is effectively
    removeCurrentDriver() + theAsioDriver = 0. The current failure branch
    only does the first of those.
  • Outcome is driver-dependent: ASIO4ALL's driver object is static in its
    DLL and survives the COM release (→ hang / lockout); Focusrite USB
    ASIO's
    object is heap-allocated, so the next call faults (0xC0000005).
  • With the synthetic driver, the stock build (4d142945) made seven ASIO
    calls on the freed object
    ; with ASIOExit(), zero.

How to reproduce

The reliable repro we have is with a synthetic driver that fails
CheckDeviceCapabilities() on demand. On real Windows hardware with ASIO4ALL
this is our best guess at hitting the same path — it is unverified:

  1. Select a device that fails the capability check (e.g. one that does not
    support the required sample rate) while a valid device is also available.
  2. Trigger re-init (e.g. change the audio device in the client settings).
  3. Re-select the previous working device.

The failure branch is hit at CSound::LoadAndInitializeDriver()
(src/sound/asio/sound.cpp) — CheckDeviceCapabilities() fails, the driver is
released via asioDrivers->removeCurrentDriver(), and the global
theAsioDriver pointer is left dangling. A subsequent re-init dereferences it.

Proposed fix

In CSound::LoadAndInitializeDriver() (src/sound/asio/sound.cpp), the
CheckDeviceCapabilities() failure branch should call ASIOExit() instead
of asioDrivers->removeCurrentDriver(), so the global pointer is nulled. The
ASIOInit()-failed branch is left alone — from reading the ASIO SDK it resets
the global pointer on that path, though I haven't instrumented it to be sure.

A ready patch exists in fork PR ann0see/jamulus#286 (one-line change + comment),
reproducing the 7-vs-0 result. This issue is opened to track the defect so the
fix is not lost if the fork PR is never reopened upstream.

Requested actions

  • Review whether the failure branch should use ASIOExit().
  • If agreed, apply the fix on main (a small, low-risk change).

⚠️ AI-generated issue — please verify

  • This text is AI generated, may be wrong, and may contain inaccuracies.
  • The hardware verification described below was performed by a human with an AI agent
    (mcfnord, on Windows).

Metadata

Metadata

Assignees

No one assigned

    Labels

    AIAI generated or potentially AI generatedWindowsWindows runtime issuebugSomething isn't working

    Type

    No type

    Projects

    Status
    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions