From 8007e043594b86ed65a4f487c9fb97bd00f13d07 Mon Sep 17 00:00:00 2001 From: Luigi Colluto Date: Sat, 11 Jul 2026 19:04:56 +0200 Subject: [PATCH] Cross-check tensor data size against shape in FP8/FP4 dequant A safetensors tensor's shape and its data_offsets byte range are independent header fields. db_read() sizes the buffer (st_value.nbytes) from data_offsets, while dequant_fp8_weight() / dequant_fp4_weight() index w->data and scale->data by shape. They were never cross-validated, so a weight that declares a large shape but a tiny data_offsets range (e.g. shape [128,128] = 16384 elements with only 128 bytes of data) makes the loops read past the end of the heap buffer -- a heap-buffer-overflow read, leaking adjacent heap bytes into the produced GGUF. Before the loops, require nbytes to cover the shape-driven access: one byte per element for the F8_E4M3 / I8 weights and F8_E8M0 scales. --- gguf-tools/deepseek4-quantize.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/gguf-tools/deepseek4-quantize.c b/gguf-tools/deepseek4-quantize.c index 3955b4352..1db04e0ab 100644 --- a/gguf-tools/deepseek4-quantize.c +++ b/gguf-tools/deepseek4-quantize.c @@ -691,6 +691,15 @@ static float *dequant_fp8_weight(const st_value *w, const st_value *scale, int64 const int64_t scale_rows = out_dim / block_out; const int64_t scale_cols = in_dim / block_in; if (scale->shape[0] != scale_rows || scale->shape[1] != scale_cols) die("FP8 scale shape mismatch"); + /* shape and data_offsets are independent header fields; cross-check that the + * on-disk buffers (sized from data_offsets by db_read) are actually large + * enough for the shape-driven indexing below. Without this, a weight that + * declares a large shape but a tiny data_offsets range makes the loops read + * past the end of w->data / scale->data (heap-buffer-overflow read). One + * byte per element for F8_E4M3 weights and F8_E8M0 scales. */ + if (w->nbytes < (size_t)out_dim * (size_t)in_dim || + scale->nbytes < (size_t)scale_rows * (size_t)scale_cols) + die("FP8 tensor data smaller than its declared shape"); float *out = xmalloc((size_t)out_dim * (size_t)in_dim * sizeof(float)); for (int64_t ob = 0; ob < scale_rows; ob++) { for (int64_t ib = 0; ib < scale_cols; ib++) { @@ -721,6 +730,14 @@ static float *dequant_fp4_weight(const st_value *w, const st_value *scale, int64 if (in_dim % 32) die("FP4 in_dim is not divisible by 32"); const int64_t n_blocks = in_dim / 32; if (scale->shape[0] != out_dim || scale->shape[1] != n_blocks) die("FP4 scale shape mismatch"); + /* As in dequant_fp8_weight: cross-check the on-disk buffer sizes (from + * data_offsets) against the shape-driven indexing so a small data range + * under a large declared shape cannot drive an out-of-bounds read. The I8 + * weight packs two 4-bit values per byte -> out_dim * packed_in bytes; the + * F8_E8M0 scale is one byte per block. */ + if (w->nbytes < (size_t)out_dim * (size_t)packed_in || + scale->nbytes < (size_t)out_dim * (size_t)n_blocks) + die("FP4 tensor data smaller than its declared shape"); float *out = xmalloc((size_t)out_dim * (size_t)in_dim * sizeof(float)); for (int64_t r = 0; r < out_dim; r++) { for (int64_t b = 0; b < n_blocks; b++) {