Harden GGUF parsing: bound file-controlled lengths/offsets/dimensions#28
Harden GGUF parsing: bound file-controlled lengths/offsets/dimensions#28professor-moody wants to merge 1 commit into
Conversation
Crafted .gguf files could trigger OOB reads (SIGSEGV), an OOB write (n_dims>8), and a divide-by-zero (general.alignment=0): file-controlled length/offset/dim fields were used to index/advance into the mmap without validating against ctx->size. Add uniform bounds checks in gguf_get_key, gguf_set_data_offset, and gguf_get_tensor; guard the alignment divisor; replace the NDEBUG-compiled-out ndim assert with a real check; overflow-check num_weights. Addresses antirez#25, antirez#27.
|
Nice hardening. One gap worth noting so it isn't assumed fully closed: this bounds the metadata / tensor-info walk (key lengths, dimension counts, the offset scan in In tensor->offset = ctx->data_off + *offset; // raw file-supplied 64-bit offset
tensor->weights_data = ctx->data + tensor->offset;
Would be a natural addition here: once |
|
Thanks @gigioneggiando, good catch. You're right that this PR only bounds the metadata/tensor-info walk and not the tensor data region: |
|
Thanks @professor-moody! Sent it as #33 — it bounds |
Summary
Crafted
.gguffiles can crash gguflib's parse path: out-of-bounds reads (SIGSEGV), an out-of-bounds write (a tensor declaringn_dims > 8), and a divide-by-zero (general.alignment = 0). The shared root cause is that file-controlled length/offset/dimension fields are used to index and advance into the mmap'd buffer with no validation againstctx->size. This PR adds uniform bounds checks so malformed files are rejected instead of crashing.Addresses #25 and #27.
Changes (
gguflib.c)gguf_get_alignment_padding()— return0whenalignment == 0(a malformedgeneral.alignment=0causedoffset % 0→ SIGFPE). [Divide-by-zero (SIGFPE) in gguflib from untrustedgeneral.alignmentvalue #27]gguf_get_key()— bound the length prefix, key name, and type tag againstctx->sizebefore dereferencing. [Out-of-bounds write in gguf_get_tensor when n_dims > 8 (CWE-787), plus OOB reads in metadata parsing #25]gguf_set_data_offset()— bound every field in the tensor-info walk againstctx->size; stop instead of walking the offset past the mmap. [Out-of-bounds write in gguf_get_tensor when n_dims > 8 (CWE-787), plus OOB reads in metadata parsing #25]gguf_get_tensor()— rejectndim > GGUF_TENSOR_MAX_DIMwith a parse error instead ofassert(). The assert is compiled out under-DNDEBUG, leaving an out-of-bounds write to the fixed-sizetensor->dim[]. Also bound the per-dimension reads against the file. [Out-of-bounds write in gguf_get_tensor when n_dims > 8 (CWE-787), plus OOB reads in metadata parsing #25]num_weightsaccumulation (a crafted dimension could otherwise wrap it).Testing
clang -O3. The minimal PoCs from Out-of-bounds write in gguf_get_tensor when n_dims > 8 (CWE-787), plus OOB reads in metadata parsing #25/Divide-by-zero (SIGFPE) in gguflib from untrustedgeneral.alignmentvalue #27 (alignment div-by-zero,gguf_get_keyOOB read, tensor-info-walk OOB read, metadata / data-offset OOB reads) now reject the file cleanly instead of SIGSEGV/SIGFPE.gguf-tools showproduces byte-identical output to the unpatched build on valid.gguffiles.No RCE is claimed — these are memory-safety crashes (DoS / OOB) on attacker-supplied files.