Commit a6ea155d3 for llama.cpp
commit a6ea155d3d38b3f6f43d0c0dc29c2d592413ef44
Author: Xiang Chen <46052474+x14ngch3n@users.noreply.github.com>
Date: Wed Sep 30 04:46:20 2026 +0800
gguf : reject tensor size that wraps after padding (#26979)
GGML_PAD(nbytes, alignment) wraps to 0 when nbytes is within
(alignment - 1) of SIZE_MAX, which silently bypassed the size
overflow guard in gguf_init_from_reader. Reject the tensor before
padding when nbytes + (alignment - 1) would overflow.
Adds a test-gguf handcrafted case (F32, ne = [4, 2^30-1, 2^30+1, 1])
whose ggml_nbytes = 2^64 - 16 lands in the wrap window. Fails on
master, passes with the guard.
diff --git a/ggml/src/gguf.cpp b/ggml/src/gguf.cpp
index 5c26f751f..8e8c2f0e6 100644
--- a/ggml/src/gguf.cpp
+++ b/ggml/src/gguf.cpp
@@ -792,7 +792,16 @@ static struct gguf_context * gguf_init_from_reader(const struct gguf_reader & gr
gguf_free(ctx);
return nullptr;
}
- size_t padded_size = GGML_PAD(ggml_nbytes(&ti.t), ctx->alignment);
+ const size_t nbytes = ggml_nbytes(&ti.t);
+ // GGML_PAD adds (alignment - 1) to nbytes. when nbytes is near
+ // SIZE_MAX this wraps to 0 and makes the check below dead.
+ if (nbytes > SIZE_MAX - (ctx->alignment - 1)) {
+ GGML_LOG_ERROR("%s: tensor '%s' size %zu overflows after padding (alignment %zu)\n",
+ __func__, ti.t.name, nbytes, ctx->alignment);
+ gguf_free(ctx);
+ return nullptr;
+ }
+ const size_t padded_size = GGML_PAD(nbytes, ctx->alignment);
if (SIZE_MAX - ctx->size < padded_size) {
GGML_LOG_ERROR("%s: tensor '%s' size overflow, cannot accumulate size %zu + %zu\n",
__func__, ti.t.name, ctx->size, padded_size);
diff --git a/tests/test-gguf.cpp b/tests/test-gguf.cpp
index f40d6984b..6c63ecaeb 100644
--- a/tests/test-gguf.cpp
+++ b/tests/test-gguf.cpp
@@ -40,6 +40,7 @@ enum handcrafted_file_type {
HANDCRAFTED_TENSORS_ZERO_DIM = 35 + offset_has_tensors,
HANDCRAFTED_TENSORS_NE_TOO_BIG = 40 + offset_has_tensors,
HANDCRAFTED_TENSORS_NBYTES_TOO_BIG = 45 + offset_has_tensors,
+ HANDCRAFTED_TENSORS_NBYTES_PAD_WRAP = 46 + offset_has_tensors,
HANDCRAFTED_TENSORS_BAD_TYPE = 50 + offset_has_tensors,
HANDCRAFTED_TENSORS_BAD_OFFSET = 60 + offset_has_tensors,
HANDCRAFTED_TENSORS_DUPLICATE_NAME = 70 + offset_has_tensors,
@@ -80,6 +81,7 @@ static std::string handcrafted_file_type_name(const enum handcrafted_file_type h
case HANDCRAFTED_TENSORS_ZERO_DIM: return "TENSORS_ZERO_DIM";
case HANDCRAFTED_TENSORS_NE_TOO_BIG: return "TENSORS_NE_TOO_BIG";
case HANDCRAFTED_TENSORS_NBYTES_TOO_BIG: return "TENSORS_NBYTES_TOO_BIG";
+ case HANDCRAFTED_TENSORS_NBYTES_PAD_WRAP: return "TENSORS_NBYTES_PAD_WRAP";
case HANDCRAFTED_TENSORS_BAD_TYPE: return "TENSORS_BAD_TYPE";
case HANDCRAFTED_TENSORS_BAD_OFFSET: return "TENSORS_BAD_OFFSET";
case HANDCRAFTED_TENSORS_DUPLICATE_NAME: return "TENSORS_DUPLICATE_NAME";
@@ -250,6 +252,13 @@ static FILE * get_handcrafted_file(const unsigned int seed, const enum handcraft
tensor_configs[1] = { GGML_TYPE_I8, { 0x7FFFFFFFFFFFFFC0, 1, 1, 1 } };
}
+ if (hft == HANDCRAFTED_TENSORS_NBYTES_PAD_WRAP) {
+ tensor_configs.resize(1);
+ // F32 with ne = [4, 2^30-1, 2^30+1, 1] so ggml_nbytes = 2^64 - 16.
+ // this hits the GGML_PAD wrap window: pad wraps to 0.
+ tensor_configs[0] = { GGML_TYPE_F32, { 4, INT64_C(1073741823), INT64_C(1073741825), 1 } };
+ }
+
if (hft == HANDCRAFTED_HEADER_BAD_N_TENSORS) {
const uint64_t n_tensors = -1;
helper_write(file, n_tensors);
@@ -774,6 +783,7 @@ static std::pair<int, int> test_handcrafted_file(const unsigned int seed) {
HANDCRAFTED_TENSORS_ZERO_DIM,
HANDCRAFTED_TENSORS_NE_TOO_BIG,
HANDCRAFTED_TENSORS_NBYTES_TOO_BIG,
+ HANDCRAFTED_TENSORS_NBYTES_PAD_WRAP,
HANDCRAFTED_TENSORS_BAD_TYPE,
HANDCRAFTED_TENSORS_BAD_OFFSET,
HANDCRAFTED_TENSORS_DUPLICATE_NAME,