Skip to content

Harden GGUF parsing: bound file-controlled lengths/offsets/dimensions#28

Open
professor-moody wants to merge 1 commit into
antirez:mainfrom
professor-moody:harden-gguf-parse-bounds
Open

Harden GGUF parsing: bound file-controlled lengths/offsets/dimensions#28
professor-moody wants to merge 1 commit into
antirez:mainfrom
professor-moody:harden-gguf-parse-bounds

Conversation

@professor-moody

Copy link
Copy Markdown

Summary

Crafted .gguf files can crash gguflib's parse path: out-of-bounds reads (SIGSEGV), an out-of-bounds write (a tensor declaring n_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 against ctx->size. This PR adds uniform bounds checks so malformed files are rejected instead of crashing.

Addresses #25 and #27.

Changes (gguflib.c)

Testing

No RCE is claimed — these are memory-safety crashes (DoS / OOB) on attacker-supplied files.

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.
@gigioneggiando

Copy link
Copy Markdown

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 gguf_set_data_offset), but it does not bound the tensor data region itself.

In gguf_get_tensor(), after the dimensions are read:

tensor->offset = ctx->data_off + *offset;      // raw file-supplied 64-bit offset
tensor->weights_data = ctx->data + tensor->offset;

*offset and the resulting tensor->bsize are never checked against ctx->size, so a crafted tensor offset/size still places weights_data outside the mapping. Any later read of bsize bytes from it (gguf_tensor_to_float, inspect-tensor, compare) is then an out-of-bounds read, even with this PR applied.

Would be a natural addition here: once ctx->data_off is known, verify tensor->offset <= ctx->size and tensor->bsize <= ctx->size - tensor->offset (overflow-checked) before weights_data is formed or dereferenced. Happy to send a follow-up patch on top of this if useful.

@professor-moody

Copy link
Copy Markdown
Author

Thanks @gigioneggiando, good catch. You're right that this PR only bounds the metadata/tensor-info walk and not the tensor data region: *offset and the resulting bsize in gguf_get_tensor() are never checked against ctx->size, so a crafted tensor offset/size still places weights_data outside the mapping. A follow-up that verifies tensor->offset <= ctx->size and tensor->bsize <= ctx->size - tensor->offset (overflow-checked) before weights_data is formed or dereferenced would be a welcome addition on top of this. Please do send it, happy to review and rebase.

@gigioneggiando

Copy link
Copy Markdown

Thanks @professor-moody! Sent it as #33 — it bounds tensor->offset/bsize against ctx->size (overflow-safe) before weights_data is formed, exactly as you described. It only touches the offset/bsize tail of gguf_get_tensor, not the ndim/dimension area this PR changes, so it should apply cleanly next to #28; happy to rebase on top if #28 lands first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants