Skip to content

fix(ruvllm): refuse malformed GGUF alignment and tensor ranges - #1204

Merged
ruvnet merged 2 commits into
ruvnet:mainfrom
ohdearquant:fix/gguf-reader-bounds
Oct 24, 2026
Merged

ruvnet merged 2 commits into
ruvnet:mainfrom
ohdearquant:fix/gguf-reader-bounds

Conversation

@ohdearquant

Copy link
Copy Markdown
Contributor

Pull request opened by an AI agent on behalf of @ohdearquant.

Summary

GgufFile now returns an error for the malformed files described in #1202 instead of panicking or reading the wrong bytes. All changes are in crates/ruvllm/src/gguf/mod.rs.

  • Alignment. general.alignment is converted to usize with a checked conversion, and the offset rounding returns RuvLLMError::Model for a zero alignment or when rounding overflows. Before this change, a zero alignment caused a division by zero, and u64::MAX overflowed in debug builds and wrapped the data offset to 0 in release builds.
  • Tensor ranges. A new private helper computes each tensor's byte range with checked arithmetic (element count, byte size, data offset plus tensor offset, end offset) and checks it against the length of the file or mapping. Each declared tensor is checked when the file is opened, in open and in both open_mmap variants. The owned reads in read_tensor_bytes check again before allocating or slicing.
  • Unchanged. Public signatures, error types and dependencies are unchanged. Any positive alignment, including one that is not a power of two, is read as before.

Behaviour on the files from #1202

The example program from #1202, built at this commit with default features (debug and release give the same output):

File open open_mmap
alignment 0 Model error: GGUF alignment must be nonzero same
alignment u64::MAX Model error: GGUF alignment overflow same
tensor offset past end of file Model error: GGUF tensor range outside file: test.weight (4224..4228, length 132) same
valid, alignment 32 loads [1.0] loads [1.0]
valid, no alignment key loads [1.0] loads [1.0]

On main (see #1202), alignment 0 panics; u64::MAX panics in debug builds and, in release builds, opens and returns the GGUF magic as the tensor's value; and the offset past the end panics through open_mmap (through open it already failed with a read error when the tensor was loaded).

Tests

Linux x86_64, rustc 1.98.0, at this commit:

Command Result
rustfmt --edition 2021 --check crates/ruvllm/src/gguf/mod.rs pass
cargo clippy --locked -p ruvllm --all-targets -- -W warnings pass, no warnings in gguf/mod.rs (none before either)
cargo test --locked -p ruvllm --lib gguf::tests:: 9 passed
same, --release 9 passed
same, --features mmap 9 passed
same, --release --features mmap 9 passed

Six new tests write small GGUF files and check, through both open and open_mmap: valid files with the default alignment, an explicit 32 and a non-power-of-two 3 still load; zero and u64::MAX alignments are refused; tensor offsets past the end of the file are refused at open and again on read; and tensor offsets and sizes whose arithmetic overflows are refused on read. Each of the seven new checks was disabled on its own (removed, or replaced with unchecked arithmetic) and its test failed; with the check restored the test passed.

Notes

  • llama.cpp's reader is stricter: it requires general.alignment to be a non-zero power of two stored as a u32 (ggml/src/gguf.cpp, lines 622 to 636 at f2cc7282). I kept the existing behaviour of accepting any positive alignment, so no file that opens today is newly refused for its alignment. I can add the power-of-two rule if you prefer to match llama.cpp.
  • A file in which any declared tensor extends past the end of the file now fails to open. Before, it opened and failed (or panicked) only when that tensor was read.
  • tensor_data (the borrowed accessor) still slices without its own check. Tensor info read from the file is now checked when the file is opened. Info that a caller builds or modifies afterwards is not.
  • This does not conflict with feat(ruvllm): write GGUF F16 and Q8_0 from SafeTensors checkpoints #1201 or feat(ruvllm): load PEFT LoRA adapters through LlmBackend #1203 (both merge cleanly with it).

Refs #1202

A zero `general.alignment` no longer panics, and an oversized one no
longer wraps the data offset in release builds; both are now an error
when the file is opened. Tensor ranges are checked when a file is
opened and again on every owned read, so a tensor whose offset or size
runs past the end of the file, or whose offset arithmetic overflows,
returns an error instead of panicking or reading the wrong bytes.

Positive alignments, including ones that are not a power of two, are
read as before.

Refs ruvnet#1202
@ohdearquant

Copy link
Copy Markdown
Contributor Author

Comment posted by an AI agent on behalf of @ohdearquant.

CI at this head (373c5e3e, tested as a merge into 31549f79): three jobs fail, none in code this pull request changes (it touches only crates/ruvllm/src/gguf/mod.rs).

  • Tests (core-and-rest) and npm audit (npm/ workspace) fail the same way on main at ba5cb62c, the latest main commit these jobs ran on (31549f79 is a binaries-only commit with no test run). The first is a link error in ruvector-typesafe-train (undefined __isoc23_strtol and related glibc symbols); the second reports 9 vulnerabilities in the npm workspace.
  • Tests (core-and-rest-heavy) failed in ruvllm-cli's serve_strict::allow_mock_serves_labeled_placeholder_responses (connection refused at tests/serve_strict.rs:138). It is the same failure seen on feat(ruvllm): write GGUF F16 and Q8_0 from SafeTensors checkpoints #1201: serve prints its "Press Ctrl+C" line before it binds the listener, and the test connects as soon as it reads that line. The test does hand the server a file that is not GGUF, but it got past its banner checks: the server rejected the file and reported mock mode, as on main. It failed only at the connect (line 187, then 138). The job passes on main.

@ruvnet

ruvnet commented Oct 24, 2026

Copy link
Copy Markdown
Owner

Verified locally together with #1200/#1204/#1203 merged onto current main: cargo fmt --check, cargo check -p ruvllm --no-default-features, cargo test -p ruvllm --lib (1656 passed) and clippy -D warnings on the lib are clean; the e2e test target builds. Merging.

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