PoC β llama.cpp control-vector integer overflow β heap OOB write
Proof-of-concept model file for a huntr Model File Vulnerabilities report against
ggml-org/llama.cpp @ d4cff11.
This repository contains a deliberately malformed GGUF control-vector file for authorized security research (responsible disclosure via huntr). It is not a usable model.
The bug (summary)
When llama.cpp loads a control vector (--control-vector foo.gguf), each tensor named direction.<N>
contributes its data at layer offset N, parsed from the tensor name with std::stoi and with
no upper bound. Both the buffer-sizing expression and the destination-pointer expression multiply
n_embd * layer_idx in 32-bit int, which overflows for large N:
// common/common.cpp (common_control_vector_load_one)
:1824 layer_idx = std::stoi(name.substr(dotpos + 1)); // from tensor NAME, no upper bound
:1852 result.n_embd = ggml_nelements(tensor); // int, set by first tensor
:1860 result.data.resize(std::max(result.data.size(), static_cast<size_t>(result.n_embd * layer_idx)), 0.0f);
// ^^^^^^^^^^^^^^^^^^^^^^^ int*int overflow
:1863 float * dst = result.data.data() + result.n_embd * (layer_idx - 1); // int*int overflow -> negative offset
:1865 dst[j] += src[j] * load_info.strength; // OOB WRITE of attacker floats
By choosing N so the product wraps, resize() does not grow the buffer while dst is computed to
lie before the allocation β attacker-controlled heap out-of-bounds (underflow) write. Both the
write offset (tensor name) and the payload (tensor F32 data Γ strength) are attacker-controlled.
Concrete overflow (in this PoC file)
Two 1-D F32 tensors: direction.1 (4 elements β sets n_embd = 4, grows buffer to 4 floats) and
direction.1073741824 (2^30). Then: n_embd*layer_idx = 4*2^30 = 2^32 β‘ 0 (int wrap) β buffer not
grown; n_embd*(layer_idx-1) = 2^32-4 β‘ -4 (int) β dst = data() - 16 bytes β 16-byte heap underflow
write.
Files
| file | description |
|---|---|
poc_ctrlvec_overflow.gguf |
valid GGUF control vector with direction.1 and direction.1073741824. |
gen_ctrlvec_poc.py |
pure-python generator (no deps). |
Reproduce
llama-cli -m <any-small-model.gguf> --control-vector poc_ctrlvec_overflow.gguf -p "hi"
Build llama.cpp with -fsanitize=address to observe the heap-buffer-overflow at common/common.cpp:1865.
The root cause and 32-bit-wrap arithmetic are verified line-by-line against source; the offset/payload
are fully attacker-controlled.
Novelty
Not in ggml/src/gguf.cpp (the file covered by the oss-sec May-2026 advisory) β this is consumer/glue
arithmetic in common/common.cpp control-vector loading, unrelated to the GGUF parser itself.
Suggested fix
Widen to 64-bit before multiplying and bound-check layer_idx against the model's layer count:
if (layer_idx <= 0 || (size_t)layer_idx > (size_t)n_layers) return; // reject
const size_t off = (size_t)result.n_embd * (size_t)(layer_idx - 1);
- Downloads last month
- 13
We're not able to determine the quantization variants.