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:
cpp
1// common/common.cpp (common_control_vector_load_one)2:1824 layer_idx = std::stoi(name.substr(dotpos +1));// from tensor NAME, no upper bound3:1852 result.n_embd =ggml_nelements(tensor);// int, set by first tensor4:1860 result.data.resize(std::max(result.data.size(),static_cast<size_t>(result.n_embd * layer_idx)),0.0f);5// ^^^^^^^^^^^^^^^^^^^^^^^ int*int overflow6:1863float* dst = result.data.data()+ result.n_embd *(layer_idx -1);// int*int overflow -> negative offset7: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.
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:
cpp
1if(layer_idx<=0||(size_t)layer_idx >(size_t)n_layers)return;// reject2const size_t off =(size_t)result.n_embd *(size_t)(layer_idx -1);