Mozilla-Ocho/llamafile β signed/unsigned mismatch in get_zip_cfile_compressed_size() -> mmap() size underflow -> OOB read / SIGBUS
Status: gated, manual-approval PoC repository. For authorized security research / bug-bounty triage only (huntr.com MFF).
Target
- Project: Mozilla-Ocho/llamafile
- Verified against commit:
84e3e1ae0f7f17d613f80539e10fc47ae9b1a6a7(2026-07-02) - Files:
llamafile/llamafile.c(llamafile_open_zip(), the sink),llamafile/zip.c(root cause,get_zip_cfile_compressed_size()) - Class: CWE-195/CWE-191 (signed/unsigned conversion error, integer underflow) -> undersized
mmap()-> CWE-125 heap/mapping out-of-bounds read (SIGBUS)
This is a different bug from this campaign's already-confirmed zip64-extra-field-parser
OOB read in the same file (a missing bounds check before a pointer dereference while walking
zip64 extra records). This finding is a distinct root cause: a signed sentinel value silently
becoming SIZE_MAX and then wrapping an unsigned mmap() size calculation.
Root cause
get_zip_cfile_compressed_size() (real zip.c, unmodified) returns int64_t and uses -1 as
an explicit "couldn't find it" sentinel when a ZIP64 escape marker (0xFFFFFFFF) is present in
the 32-bit compressed-size field but no valid ZIP64 extra field backs it up. The real
llamafile_open_zip() (llamafile.c) assigns this signed return value directly into
file->size, a size_t (unsigned) struct field:
// llamafile.c:178 (struct llamafile { ... size_t size; ... size_t mapsize; ... };)
file->size = get_zip_cfile_compressed_size(cdirdata + entry_offset);
-1 silently becomes SIZE_MAX. That poisoned value then flows into an unsigned addition a few
lines later:
// llamafile.c:227
file->mapsize = skew + file->size; // skew (small) + SIZE_MAX wraps back down to a tiny number
file->mapping = mmap(0, file->mapsize, PROT_READ, MAP_SHARED, fd, mapoff);
mmap() maps only the tiny wrapped size, but every later read against the file (e.g.
llamafile_read(), llamafile_size()) trusts the original poisoned file->size (still
SIZE_MAX) as the "how much data is really here" bound β so any read past the first few bytes
walks off the real mapping into unmapped memory.
Methodology
get_zip_cfile_compressed_size()/get_zip_cfile_offset() are compiled in from the REAL,
unmodified zip.c/zip.h (fetched at the pinned commit; reused from this campaign's earlier
llamafile build work). The mmap/read sequence from llamafile_open_zip() is reproduced with the
exact real variable roles and the exact real lines (cross-checked byte-for-byte against the real
fetched llamafile.c, included in this repo for verification), since llamafile.c itself
depends on Cosmopolitan-specific machinery (gc(), GetProgramExecutableName(), etc.) not
available on stock Linux β none of those substitutions touch the vulnerable arithmetic itself,
which lives entirely in the real, verbatim zip.c functions plus the file->mapsize = skew + file->size line.
Proof of concept (run_output.txt)
[VULN] get_zip_cfile_offset() = 0 (valid, small)
[VULN] get_zip_cfile_compressed_size() = -1 <-- returned -1 (sentinel, no ZIP64 backing)
[VULN] file->size (size_t, after assignment) = 18446744073709551615 (0xffffffffffffffff) <-- silently became SIZE_MAX!
...
[VULN] file->mapsize = skew + file->size = 36 + 18446744073709551615 = 35 (0x23)
[VULN] ^^^ wrapped around to a TINY value instead of an error/huge value!
[mmap] mmap(size=35) SUCCEEDED at 0x7f1325180000 -- real mapped extent is only ~35 bytes
[crash-test] forking a child to attempt a 64 MiB read via the exact
[crash-test] llamafile_read() logic (memcpy bounded by the poisoned
[crash-test] file->size, not the real ~35-byte mapping)...
[CONFIRMED] child process was KILLED by signal 7 (Bus error) --
[CONFIRMED] this is the real, dynamically-triggered out-of-bounds read crash.
Every claimed line was cross-checked against the real llamafile.c fetched at the pinned
commit β confirmed byte-for-byte matches at:
llamafile.c:178:file->size = get_zip_cfile_compressed_size(cdirdata + entry_offset);llamafile.c:227:file->mapsize = skew + file->size;llamafile.c:59-68:struct llamafile { ... size_t size; ... size_t mapsize; ... };
Reachability
Every .llamafile / foo.zip@weights.gguf load goes through llamafile_open_zip(). An
attacker supplying a crafted ZIP/llamafile container with a central-directory entry whose
compressed-size field is the ZIP64 escape marker (0xFFFFFFFF) and no backing ZIP64 extra
record triggers this on ordinary model load.
Files
poc_harness.cβ the verification harness (craft + repro, see file header for full detail)zip.c/zip.hβ real, unmodified files from Mozilla-Ocho/llamafile at the pinned commitllamafile.cβ real, unmodifiedllamafile.cfetched at the pinned commit, included so the claimed vulnerable lines can be independently verified against itrun_output.txtβ captured run output showing the wraparound and the SIGBUS crashmalicious.llamafileβ the crafted 130-byte trigger file
Build & run
gcc -O0 -g -Wall -include stdint.h -o poc poc_harness.c zip.c
./poc
Independent re-verification (2026-07-11)
Rebuilt from these exact source files with a fresh gcc -O0 -g -Wall -include stdint.h
invocation and re-ran the resulting binary independently (not reusing any cached build or
prior run's captured output). Fresh output matched the documented behavior exactly:
get_zip_cfile_compressed_size() returned -1, file->size became SIZE_MAX
(18446744073709551615), file->mapsize wrapped to 35, mmap(size=35) succeeded, and the
forked child performing a bounded-by-file->size read was killed by signal 7 (Bus error).
Also re-confirmed the two cited line numbers (llamafile.c:178, llamafile.c:227) via direct
grep against the unmodified llamafile.c included in this bundle. Dedup search re-run same
night: no CVE/GHSA/GitHub issue found for this function or bug class in either
Mozilla-Ocho/llamafile or its successor mozilla-ai/llamafile.
- Downloads last month
- -