Skip to content

forced_align (CPU) segfaults when frames * (2*targets+1) exceeds 2^31: 32-bit loop index overflow #4208

Description

@happyarts

🐛 Describe the bug

torchaudio.functional.forced_align (CPU) crashes the whole process with SIGSEGV when frames * (2 * target_length + 1) exceeds 2^31. This is reachable with realistic inputs: character-level CTC alignment (WhisperX-style) of a ~20-minute chunk of dense speech at 50 fps crosses the limit (we hit it in production aligning a German podcast).

Repro

# needs ~2.2 GB RAM; crashes the interpreter (run in a subprocess)
import torch, torchaudio

T, L, C = 70_000, 15_500, 30            # T*(2L+1) = 2.17e9 > 2^31
g = torch.Generator().manual_seed(0)
emission = torch.log_softmax(torch.rand((T, C), generator=g), dim=-1)
targets = torch.randint(1, C, (1, L), generator=g, dtype=torch.int32)

torchaudio.functional.forced_align(emission.unsqueeze(0), targets, blank=0)
# -> SIGSEGV / SIGBUS

Observed behaviour (torchaudio 2.11.0, macOS arm64):

T * (2L+1) targets dtype result
2.17e9 (> 2^31) int32 💥 SIGSEGV
2.17e9 (> 2^31) int64 💥 SIGSEGV (no dtype workaround)
2.10e9 (< 2^31) int32 ✅ correct result, ~8 s

Root cause

src/libtorchaudio/forced_align/cpu/compute.cpp, the backPtr_a init loop uses a 32-bit loop index against a 64-bit trip count:

auto backPtr_a = new int8_t[T * S];     // T, S are int64_t -- allocation is fine
for (int i = 0; i < T * S; i++) {       // int i overflows at 2^31 (UB)
  backPtr_a[i] = -1;
}

Once i wraps to INT_MIN, the store lands at backPtr_a - 2^31 bytes. The macOS crash report confirms this exactly: fault address = buffer base − 0x80000000, as a byte write inside forced_align_impl<float, int>:

Exception Type:    EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000892e94000
  x0/x19 (buffer): 0x0000000912e94000       <- base - fault = 0x80000000
0  libtorchaudio.abi3.so  forced_align_impl<float, int>(...) + 316
1  libtorchaudio.abi3.so  torchaudio::alignment::cpu::compute(...) + 1384

This also explains why int64 targets crash identically — the dispatch only changes target_t, not the init loop. The alphas_a init loop two lines above uses the same int i < 2 * S idiom (not practically reachable today, but the same latent pattern).

The bug is platform-independent (plain C++ signed-overflow UB); it is present in current main (c0cbdb9) and at least back to the 2.8/2.11 releases.

Fix

One-liner: make the loop indices int64_t. PR incoming.

Versions

  • torchaudio 2.11.0 (also inspected current main, c0cbdb9)
  • torch 2.13.0
  • Python 3.12.8, macOS 26.5 (arm64, M1 Max) — but the overflow is platform-independent

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions