File size: 2,516 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
# 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.)
```