Skip to content

fix: reject negative offset_ms and oversized audio before calling whisper.cpp - #16

Open
rmorse wants to merge 2 commits into
developfrom
fix/negative-offset
Open

rmorse wants to merge 2 commits into
developfrom
fix/negative-offset

Conversation

@rmorse

@rmorse rmorse commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

Release notes (draft)

User-facing summary, kept current for reuse in the release notes. Mirrors the CHANGELOG.md entries added here.

Fixed

  • Out-of-bounds read in whisper.cpp from a negative offset_ms. A negative FullParams::offset_ms became a negative mel offset in whisper.cpp's encoder, which then read before the start of the mel buffer. This was reachable from safe code through every transcription path, including 0.1.5. Transcription now returns WhisperError::InvalidParameter for a negative offset_ms or duration_ms (WhisperState::full, and through it transcribe*, WhisperStream, WhisperStreamPcm and the temperature-fallback transcriber). WhisperState::full_parallel also rejects an offset_ms at or past the end of the audio, which whisper.cpp turned into negative chunk sizes.
  • Oversized audio is rejected instead of truncated. Audio with more than i32::MAX samples was passed to whisper.cpp with its length silently truncated by an as i32 cast. WhisperState::full, full_parallel, WhisperVadProcessor::detect_speech and segments_from_samples now reject it.
  • Out-of-bounds read in whisper.cpp from a VAD segment index. VadSegments::get_segment_t0() / get_segment_t1() passed the index to whisper.cpp, which does not bounds-check it, so an out-of-range index read outside the segment vector. They now panic on an out-of-range index, like slice indexing and the WhisperState result getters.

Implementation notes

  • FullParams::validate() (crate-internal) checks the parameters whisper.cpp uses unchecked; WhisperState::full and full_parallel call it before entering C. The offset_ms setter still accepts any value, so the error surfaces at transcription, consistent with other parameter errors.
  • state::sample_count() (crate-internal) replaces the audio.len() as i32 casts with a checked conversion, shared with vad.rs.
  • The VAD getter fix follows the convention from the earlier WhisperState getter fix: plain-value getters panic on a bad index, like slice indexing. Evidence: whisper_vad_segments_get_segment_t0/t1 return segments->data[i_segment] with no check (whisper.cpp:5347-5352 at the pinned commit). Raised by the external review of the API-layers design.
  • Found during review of the whisper.cpp API-layers design (feat: whisper.cpp 1.9 API catch-up #15). Evidence: seek_start = params.offset_ms/10 (whisper.cpp:7018) flows into whisper_encode_internal, where i0 = std::min(mel_offset, n_len) stays negative and mel_inp.data[j*n_len + i] is read for negative i (whisper.cpp:2433-2438), at the pinned commit.
  • Branched from develop independently of feat: whisper.cpp 1.9 API catch-up #15. feat: whisper.cpp 1.9 API catch-up #15 removes WhisperState::full_parallel, so rebasing feat: whisper.cpp 1.9 API catch-up #15 onto this will drop the full_parallel hunk; the design for feat: whisper.cpp 1.9 API catch-up #15 carries the same validation into the new context-layer full_parallel and transcribe_parallel.

Validation

Windows / MSVC, test models from cargo xtask test-setup:

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets -- -D warnings and --features async
  • cargo test --workspace -- --test-threads=1: 125 passed, none skipped (all model loads used the real ggml-tiny.en.bin)
  • cargo test -p whisper-cpp-plus --features async -- --test-threads=1: 112 passed
  • New tests: FullParams::validate unit test; integration test that negative offset_ms/duration_ms are rejected by WhisperState::full, transcribe_with_full_params and full_parallel, that full_parallel rejects an offset past the end, and that valid offsets still work; VAD integration test that get_segment_t0/t1 panic for indices n, n+1, -1, i32::MIN, i32::MAX and for index 0 on a zero-segment result, while valid indices still work.

…sper.cpp

A negative FullParams::offset_ms became a negative mel offset in
whisper.cpp's encoder (seek_start = offset_ms/10, whisper.cpp:7018), which
then read before the start of the mel buffer (2433-2438): an out-of-bounds
read reachable from safe code through every transcription path, including
0.1.5.

WhisperState::full (used by transcribe*, the streams and the fallback
transcriber) and full_parallel now validate offset_ms/duration_ms, and
full_parallel also rejects an offset at or past the end of the audio. Sample
counts above i32::MAX are rejected instead of being truncated by an `as i32`
cast, here and in WhisperVadProcessor.
whisper.cpp indexes the VAD segment vector without a bounds check, so an
out-of-range index passed to get_segment_t0/t1 was an out-of-bounds read
reachable from safe code. The getters now panic on an out-of-range index,
matching the WhisperState result getters.
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