Skip to content

Conversation

@sinkingsugar
Copy link

Summary

Fixes undefined behavior in CoreAudio backend that causes crashes under AddressSanitizer and ThreadSanitizer. This was introduced in #943 when migrating from coreaudio-rs to objc2-core-audio.

The Bug

In 10 locations across macos/enumerate.rs and macos/device.rs, data_size variables were declared immutable but passed to CoreAudio APIs that write to them:

// BROKEN - causes UB
let data_size = 0u32;
AudioObjectGetPropertyDataSize(..., NonNull::from(&data_size), ...)
//                                   ^^^^^^^^^^^^^^^^^^^^^^^^^ immutable ref!

CoreAudio's size parameter is inout - the API writes the actual data size back. Creating NonNull from an immutable reference and then writing through it is undefined behavior.

Why It Wasn't Caught Earlier

The old raw pointer code hid the UB with unsafe casts:

&data_size as *const _ as *mut _  // Unsafe cast bypasses Rust's type system

The new objc2-core-audio API properly enforces mutability contracts via NonNull, exposing the latent bug. Normal builds work fine because at the machine level the stack memory is writable, but sanitizers enforce Rust's safety contracts and crash.

The Fix

Changed all affected variables to mut and updated NonNull calls:

// FIXED
let mut data_size = 0u32;
AudioObjectGetPropertyDataSize(..., NonNull::from(&mut data_size), ...)

Files changed:

  • src/host/coreaudio/macos/enumerate.rs: 2 data_size variables, 4 API calls
  • src/host/coreaudio/macos/device.rs: 7 data_size variables, 14 API calls

Reproducing the Bug

# With cpal 0.16 (before this fix):
cargo +nightly test -Zbuild-std --target aarch64-apple-darwin \
  --lib --verbose
RUSTFLAGS="-Zsanitizer=address"
# ☠️ CRASH: AddressSanitizer: SEGV on unknown address

The existing tests (test_play, test_record) trigger device enumeration, which hits the buggy code.

Bonus: Sanitizer CI Workflow

This PR includes a new .github/workflows/sanitizers.yml that would have caught this bug before release. It runs ASAN and TSAN on both macOS (CoreAudio) and Linux (ALSA) since UB can be platform-specific.

Benefits:

  • Catches memory safety bugs, use-after-free, buffer overflows (ASAN)
  • Catches race conditions, data races (TSAN)
  • Runs on existing tests with no code changes needed
  • Would have prevented this UB from reaching 0.16 release

Checklist

  • Fixed all 10 instances of the bug
  • Verified fix compiles (cargo check)
  • Tested with sanitizers locally (crashes fixed)
  • Added sanitizer CI to prevent future UB

Related

…tizer crashes

In audio_devices(), data_size was declared as immutable but AudioObjectGetPropertyDataSize
needs to write the actual property size into it. This caused undefined behavior that
sanitizers (ASAN/TSAN) detected as writes to immutable memory.

This bug was introduced in commit ed9d643 when migrating from coreaudio-rs to objc2-core-audio.
The old raw pointer code hid the UB with an unsafe cast (&data_size as *const _ as *mut _),
but the new NonNull API properly enforces mutability contracts.

The fix changes:
- let data_size = 0u32; → let mut data_size = 0u32;
- NonNull::from(&data_size) → NonNull::from(&mut data_size)

Fixes crashes during device enumeration under AddressSanitizer and ThreadSanitizer on macOS.
Fixed 7 more instances of the same UB pattern where data_size was declared
immutable but passed to CoreAudio APIs that write to it:

In set_sample_rate() (lines 64, 81, 128):
- AudioObjectGetPropertyData writes the actual data size back
- Both calls to get sample rates and sample rate ranges

In Device::id() (line 376):
- AudioObjectGetPropertyData for kAudioDevicePropertyDeviceUID

In Device::supported_configs() (lines 420, 471):
- AudioObjectGetPropertyDataSize writes the required buffer size
- AudioObjectGetPropertyData confirms the size

In Device::default_config() (line 598):
- AudioObjectGetPropertyData for stream format

All of these were causing sanitizer crashes because the CoreAudio APIs
expect mutable references (the size parameter is 'inout').
Added comprehensive sanitizer testing for both macOS and Linux:
- AddressSanitizer (ASAN) for memory safety bugs
- ThreadSanitizer (TSAN) for race conditions and threading issues

These sanitizers run the existing test suite with runtime instrumentation
to catch undefined behavior that normal builds miss. This would have caught
the data_size mutability bugs fixed in the previous commits.

The workflow runs on both platforms since CoreAudio (macOS) and ALSA (Linux)
have different code paths, and UB can be platform-specific.
Copilot AI review requested due to automatic review settings November 21, 2025 02:48
Copilot finished reviewing on behalf of sinkingsugar November 21, 2025 02:49
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes critical undefined behavior in the CoreAudio backend introduced during the migration to objc2-core-audio. The bug involved passing immutable references to CoreAudio APIs that expect to write through their parameters, which sanitizers correctly detect as UB.

Key Changes:

  • Fixed 10 instances of UB by making data_size variables mutable in CoreAudio API calls
  • Added comprehensive sanitizer CI workflow to prevent future UB issues

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/host/coreaudio/macos/enumerate.rs Fixed 1 data_size variable declaration and 2 NonNull::from calls to use mutable references
src/host/coreaudio/macos/device.rs Fixed 7 data_size variable declarations and 14 NonNull::from calls to use mutable references
.github/workflows/sanitizers.yml Added new CI workflow running AddressSanitizer and ThreadSanitizer on macOS and Linux

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant