Skip to content

macOS: OOB panic in NativeSdkSpectrumComputeBands when sample rate < 32 kHz (index past Nyquist) #108

Description

@kitada-brisys

Summary

NativeSdkSpectrumComputeBands in src/platform/macos/appkit_host.m crashes with an index-out-of-bounds panic when the audio sample rate is low enough that the fixed 50 Hz–16 kHz band range extends past the Nyquist frequency (e.g. 16 kHz sample rate, common for VoIP/telephony audio).

thread 9178883 panic: index 1036 out of bounds for type 'float[1024]'
src/platform/macos/appkit_host.m:8595:17 in NativeSdkSpectrumComputeBands
            if (power[bin] > peak) peak = power[bin];
src/platform/macos/appkit_host.m:8743:10 in -[NativeSdkAppKitHost audioSpectrumTimerFired:]
    if (!NativeSdkSpectrumComputeBands(state, self.audioSpectrumFft, window, event.audio_bands)) return;

Environment

  • @native-sdk/cli 0.4.0 (@native-sdk/cli-darwin-arm64)
  • macOS (Darwin 25.5.0), Apple Silicon
  • Crash observed while running an app via native dev with audio playing at a 16 kHz sample rate (SIP/VoIP call audio)

Root cause

In the band-bucketing loop (appkit_host.m around line 8586):

int low_bin = (int)(low_hz / hz_per_bin);
int high_bin = (int)ceil(high_hz / hz_per_bin);
if (low_bin < 1) low_bin = 1;
if (high_bin > NATIVE_SDK_SPECTRUM_FFT_SIZE / 2 - 1) high_bin = NATIVE_SDK_SPECTRUM_FFT_SIZE / 2 - 1;
if (high_bin < low_bin) high_bin = low_bin;   // <-- undoes the clamp above
float peak = 0.0f;
for (int bin = low_bin; bin <= high_bin; bin += 1) {
    if (power[bin] > peak) peak = power[bin];  // <-- OOB read
}

Two problems combine:

  1. low_bin is clamped only at the bottom (< 1), never against the top of the power[FFT_SIZE / 2] array.
  2. if (high_bin < low_bin) high_bin = low_bin; re-raises high_bin above the top clamp applied on the previous line.

With sample_rate = 16000 and FFT_SIZE = 2048, hz_per_bin ≈ 7.8125. Bands whose low_hz exceeds the 8 kHz Nyquist produce low_bin ≥ 1024 (first offender lands at 1036, matching the panic), high_bin is clamped to 1023, then re-raised to low_bin, and the peak loop reads power[1036] out of bounds.

Any sample rate below 32 kHz can trigger this, since the top band edge is fixed at NATIVE_SDK_SPECTRUM_HIGH_HZ = 16 kHz.

Reproduction

  1. Build any app that enables the audio spectrum feed.
  2. Play audio whose engine sample rate is 16 kHz (e.g. wideband VoIP audio).
  3. Wait for audioSpectrumTimerFired: to run once enough samples are buffered — the app panics.

Suggested fix

Clamp low_bin against the top of the array as well, and skip bands entirely above Nyquist rather than folding them onto the last bin, e.g.:

const int max_bin = NATIVE_SDK_SPECTRUM_FFT_SIZE / 2 - 1;
int low_bin = (int)(low_hz / hz_per_bin);
int high_bin = (int)ceil(high_hz / hz_per_bin);
if (low_bin < 1) low_bin = 1;
if (low_bin > max_bin) {           // band entirely above Nyquist
    bands[band] = 0;               // silence, matches the floor-dB mapping
    continue;
}
if (high_bin > max_bin) high_bin = max_bin;
if (high_bin < low_bin) high_bin = low_bin;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions