From a6ea155d3d38b3f6f43d0c0dc29c2d592413ef44 Mon Sep 17 00:00:00 2001 From: Xiang Chen <46052474+x14ngch3n@users.noreply.github.com> Date: Wed, 30 Sep 2026 04:46:20 +0800 Subject: [PATCH] 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. --- ggml/src/gguf.cpp | 11 ++++++++++- tests/test-gguf.cpp | 10 ++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/ggml/src/gguf.cpp b/ggml/src/gguf.cpp index 5c26f751fd..8e8c2f0e68 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 f40d6984bf..6c63ecaeb4 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 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,