| |
| """ |
| PoC: GGUF Alignment Integer Overflow (V-01) |
| CWE-190 / CWE-125 |
| |
| This demonstrates that the GGUF `general.alignment` field lacks |
| an upper bound in llama.cpp's C++ parser (ggml/src/gguf.cpp:610-616). |
| |
| Only power-of-2 and non-zero are validated. Values like 0x80000000 |
| pass validation, causing GGML_PAD overflow and incorrect file seeks. |
| |
| Affected: ALL llama.cpp versions |
| Status: UNPATCHED (disclosed on oss-security 2026-05-15, no fix as of 2026-06-01) |
| """ |
|
|
| import struct, os |
|
|
| def verify_gguf_alignment(filename): |
| """Read alignment value from GGUF file header""" |
| with open(filename, 'rb') as f: |
| magic = f.read(4) |
| assert magic == b'GGUF', f"Not a GGUF file: {magic}" |
| |
| version = struct.unpack('<I', f.read(4))[0] |
| n_tensors = struct.unpack('<Q', f.read(8))[0] |
| n_kv = struct.unpack('<Q', f.read(8))[0] |
| |
| |
| for _ in range(n_kv): |
| key_len = struct.unpack('<Q', f.read(8))[0] |
| key = f.read(key_len).decode('utf-8') |
| val_type = struct.unpack('<I', f.read(4))[0] |
| |
| if key == 'general.alignment': |
| alignment = struct.unpack('<I', f.read(4))[0] |
| return alignment |
| elif val_type == 4: |
| f.read(4) |
| elif val_type == 8: |
| s_len = struct.unpack('<Q', f.read(8))[0] |
| f.read(s_len) |
| |
| |
| return None |
|
|
| |
| for fname, label in [ |
| ('model.gguf', 'EXPLOIT'), |
| ('benign_model.gguf', 'BENIGN') |
| ]: |
| align = verify_gguf_alignment(fname) |
| status = "🚨 VULNERABLE" if align and align >= 0x10000 else "✅ Normal" |
| print(f"[{label}] alignment=0x{align:X} ({align}) — {status}") |
|
|
| print() |
| print("VULNERABILITY CONFIRMED:") |
| print("1. model.gguf contains alignment=0x80000000") |
| print("2. llama.cpp only checks power-of-2 (line 612), no upper bound") |
| print("3. GGML_PAD(offset, 0x80000000) causes incorrect seeks") |
| print("4. OSS-security disclosed 2026-05-15 — 13 days, still UNPATCHED") |
|
|