YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

GGUF KV Array Field Unbounded Length β€” CPU/Memory Amplification DoS

Status: Reported to Huntr (pending) Package: gguf (PyPI) β€” official gguf-py reader/writer from ggml-org/llama.cpp File / function: gguf/gguf_reader.py, GGUFReader._get_field_parts() (~line 247) Class: CWE-834 (Excessive Iteration) / CWE-400 (Uncontrolled Resource Consumption), root cause CWE-20 (Improper Input Validation) Severity: High β€” a few hundred bytes of crafted input can consume gigabytes of RAM and/or hang the parsing process for minutes with no exception raised, on any application that loads a GGUF file (the standard format for llama.cpp-compatible LLM weights, used across the local-inference ecosystem: llama.cpp, Ollama, LM Studio, ktransformers, and many others that reuse this same gguf library).

Summary

When GGUFReader parses a key/value metadata field of type ARRAY, it reads the declared element count (alen) as a raw uint64 straight from the file β€” with no upper bound check β€” and then uses it directly as a Python loop counter:

# gguf/gguf_reader.py, GGUFReader._get_field_parts()
alen = self._get(offs, np.uint64)
offs += int(alen.nbytes)
aparts: list[npt.NDArray[Any]] = [raw_itype, alen]
data_idxs: list[int] = []
for idx in range(alen[0]):                      # <-- attacker-controlled, unbounded
    curr_size, curr_parts, curr_idxs, curr_types = self._get_field_parts(offs, raw_itype[0])
    ...
    aparts += curr_parts
    data_idxs += (idx + idxs_offs for idx in curr_idxs)
    offs += curr_size

Each loop iteration performs a numpy.memmap slice read and appends to a Python list β€” regardless of whether real data still exists in the file at that offset. numpy.memmap slicing silently clips out-of-bounds reads instead of raising an exception, so the loop never fails fast; it just keeps running (and allocating) alen times, even when the file itself is only a few hundred bytes long.

A second, related trigger point exists at the top level: GGUFReader.__init__ reads kv_count (also an unchecked uint64) from the header and passes it straight into _build_fields(offs, kv_count), which loops for _ in range(count). On a file with no real KV entries past the header, this crashes with an uncaught IndexError instead of a clean parse error β€” same root cause (CWE-20), a different and even smaller trigger.

Proof of Concept

poc_gguf_array_length_dos.py:

  1. Writes a normal, well-formed 169-byte GGUF file containing one small KV array field (5 integers).
  2. Binary-patches only the 8-byte declared-length field of that array β€” nothing else in the file changes, and the file size stays 169 bytes.
  3. Parses the patched files with the real GGUFReader and measures wall-clock time and peak RSS.
pip install gguf numpy
python poc_gguf_array_length_dos.py

Measured results (169-byte file in all cases)

Declared alen Parse time Peak RSS Amplification (RSS bytes : file bytes)
5 (legit baseline) 0.002 s 31.5 MB 1 : 195,275 (interpreter baseline)
100,000 0.76 s 125 MB 1 : 774,629
500,000 4.0 s 499 MB 1 : 3,097,957
1,000,000 8.2 s 970 MB 1 : 6,021,774
50,000,000 did not finish (60 s timeout) β€” extrapolated: tens of GB / permanent hang

RSS growth is close to linear (~1 KB per declared array element), so there is no ceiling short of process/OOM limits β€” a value near UINT64_MAX in the same 169-byte file is expected to hang or exhaust memory on essentially any machine.

Why this matters

  • Trivial to trigger: a single 8-byte field in an otherwise completely valid, small GGUF file.
  • No exception, no early warning: the process just keeps consuming memory/CPU; nothing in the calling application (llama.cpp bindings, ktransformers, model-hosting services that auto-preview/validate uploaded GGUF files, etc.) gets a chance to reject the file before resources are exhausted.
  • Format is high-value and widely reused: gguf-py is the reference GGUF reader reused by many downstream inference projects, so a fix here benefits the whole ecosystem rather than one application.

Suggested fix

Validate alen (and kv_count, tensor_count) against a sane upper bound β€” and, more robustly, against the number of bytes actually remaining in the memory-mapped file β€” before using it as a loop count or allocation size, raising a clean ValueError immediately instead of iterating.

Relationship to prior findings

This is a distinct vulnerability class from other model-file-parsing findings in the same research line:

  • Different from "CULA" (Convergent Untrusted-Length Allocation, CWE-789) findings in numpy/joblib/zarr/hickle/skops/anndata/fastparquet/keras β€” those are a single upfront np.empty()-style allocation sized from an untrusted length; this is an unbounded Python loop with per-iteration allocation (CWE-834).
  • Different from a recursion-depth finding in protobuf's json_format.ParseDict() (CWE-674) β€” this is iteration-count-based, not recursion-based.

Disclosure

Reported via Huntr under the GGUF / llama.cpp model-file-vulnerability scope. Please do not use this PoC against production systems you do not own or have explicit permission to test.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support