Views
No views yet
gguf (PyPI) — official gguf-py from ggml-org/llama.cpp
File / function: gguf/gguf_reader.py, GGUFReader._get_field_parts()
Class: CWE-674 (Uncontrolled Recursion)
Severity: Medium — an uncaught, unhandled exception crashes any application calling GGUFReader() on a ~12KB crafted file, with no exception type most code would think to catch.ARRAY, GGUFReader._get_field_parts() reads the array's element type and recursively calls itself to parse each element:1if gtype == GGUFValueType.ARRAY:
2 raw_itype = self._get(offs, np.uint32)
3 ...
4 for idx in range(alen[0]):
5 curr_size, curr_parts, curr_idxs, curr_types = self._get_field_parts(offs, raw_itype[0])raw_itype) isn't itself ARRAY, and no recursion depth limit. A GGUF file can declare an array whose elements are arrays, whose elements are arrays, nested as deep as the file's author chooses.ggml/src/gguf.cpp's type-dispatch switch:1case GGUF_TYPE_ARRAY:
2default:
3 {
4 GGML_LOG_ERROR("%s: key '%s' has invalid GGUF type %d\n", ...);
5 ok = false;
6 } break;ARRAY as an array's element type is treated as invalid input and cleanly rejected. The Python bindings have no equivalent check.poc_gguf_nested_array_recursion.py builds a series of small, otherwise-valid GGUF files with a single KV field nested to increasing depth, and shows the divergence:1pip install gguf numpy
2python poc_gguf_nested_array_recursion.py [path to a compiled llama-gguf binary, optional]| Nesting depth | File size | Python gguf-py | Native C++ reader |
|---|---|---|---|
| 1 | 70 bytes | accepted silently | rejected cleanly ("invalid GGUF type") |
| 10 | 178 bytes | accepted silently | (not tested, same as depth 1) |
| 100 | 1,258 bytes | accepted silently | — |
| 1,000 | 12,058 bytes | RecursionError: maximum recursion depth exceeded | — |
| 5,000 | 60,058 bytes | RecursionError: maximum recursion depth exceeded | — |
sys.setrecursionlimit() — it becomes an uncaught crash.RecursionError is not caught anywhere in gguf-py; it propagates all the way out of GGUFReader.__init__():Traceback (most recent call last):
File "...", line 3, in <module>
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]
...
RecursionError: maximum recursion depth exceededGGUFReader(path) without a specific except RecursionError handler — unusual, since most file-parsing code anticipates ValueError/OSError, not RecursionError — crashes with an unhandled exception.gguf-py (model preview/validation services, conversion tooling, CI harnesses testing community-submitted models) can be crashed by a ~12KB file that otherwise looks like an ordinary, if oddly-structured, metadata field._get_field_parts(), reject ARRAY as a valid array-element type the same way the C++ implementation does (this alone fixes the underlying parity gap and, as a side effect, removes the unbounded-recursion path entirely, since the recursion can only occur through nested arrays).protobuf's json_format.ParseDict() — but a completely different library, format, and code path. Also distinct from this reporter's two other gguf-py findings: "KV Array Field Unbounded Length" (CWE-834, iteration-based, not recursion) and "Tensor Data Offset Aliasing" (CWE-1284, a data-integrity issue unrelated to metadata parsing). Also distinct from a separately and independently reported n_dims/GGML_MAX_DIMS parity gap (a different structural issue — per-tensor dimension count vs. this report's array nesting).