Skip to content

Pregenerated Unix bindings are ABI-incompatible on Linux ARM64 and cause heap corruption #3

Description

@olegpulatov

Description

maudio-sys currently uses one pregenerated Unix binding file across multiple Unix
chitectures and libc environments. The bindings shipped in versions 0.1.3 and 0.1.4 appear to
ve been generated against x86-64 glibc headers and are ABI-incompatible with aarch64 glibc.

This causes maudio::Device to allocate less storage than ma_device_init_ex() expects. The
implementation then writes beyond the Rust allocation, corrupting the heap.

Environment

  • maudio: 0.1.6
  • maudio-sys: reproduced with 0.1.3 and 0.1.4
  • Rust target: aarch64-unknown-linux-gnu
  • OS: NixOS ARM64
  • libc: glibc 2.42
  • Backend: PulseAudio

This appears to be an architecture/libc ABI issue rather than a NixOS-specific issue.

ABI measurements

The pregenerated Rust bindings report:

Type Rust pregenerated binding
pthread_mutex_t / ma_mutex 40
ma_context 1024
ma_device 3776

A C translation unit compiled for aarch64-unknown-linux-gnu reports:

Type AArch64 glibc C ABI
pthread_mutex_t / ma_mutex 48
ma_context 1056
ma_device 3832

The alignments match at 8 bytes, but the sizes and internal layouts do not.

Failure

Initializing and later destroying a PulseAudio playback device eventually aborts with:

  free(): invalid size
  SIGABRT

maudio::Device allocates using Rust’s smaller ma_device layout. ma_device_init_ex() is compiled
against the larger target C layout and overruns that allocation.

##Confirmation using generated bindings

I repeated the test against the published maudio 0.1.6 / maudio-sys 0.1.4 crates with the
generate-bindings feature enabled.

With bindings generated from the target aarch64 glibc headers, an explicit PulseAudio playback
device initializes and uninitializes successfully without heap corruption.

This confirms that the issue is in the pregenerated Unix bindings rather than the PulseAudio
backend itself.

On NixOS, bindgen needed to be pointed at the target glibc headers using
BINDGEN_EXTRA_CLANG_ARGS.

Workaround

Enable target-native binding generation:

  [dependencies]
  maudio = { version = "0.1.6", features = ["generate-bindings"] }

This requires Clang/libclang and access to the target headers.

Suggested fix

Select pregenerated bindings using Cargo’s full TARGET, including:

  • architecture;
  • operating system;
  • libc/environment.

At minimum, Linux bindings should be separate for:

  • x86_64-unknown-linux-gnu
  • aarch64-unknown-linux-gnu
  • x86_64-unknown-linux-musl
  • aarch64-unknown-linux-musl

Alternatively, bindgen could run against the correct target sysroot.

Changing only aggregate size constants would not be sufficient because embedded pthread types,
field offsets, unions, alignment, and aggregate layouts all depend on the target headers.

It would also be useful to add a separately compiled C helper that returns sizeof and alignment
values for pthread_mutex_t, ma_mutex, ma_context, and ma_device, then compare those values with
Rust’s size_of and align_of.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions