From b1a5aaa93c787b058706afaf8ef27a42207c0749 Mon Sep 17 00:00:00 2001 From: Filippo Tedeschi Date: Tue, 29 Sep 2026 10:57:35 +0200 Subject: [PATCH] dictBuilder: validate NULL buffer inputs and arithmetic bounds In dictBuilder interfaces, callers providing NULL buffers or sample size arrays could cause NULL pointer dereferences prior to capacity validation: 1. ZDICT_getDictID() and ZDICT_getDictHeaderSize() did not verify whether dictBuffer was non-NULL before reading magic headers with MEM_readLE32. 2. ZDICT_totalSampleSize() and COVER_sum() did not guard against NULL pointers in the sample size array, and lacked overflow protection when accumulating total sample sizes. 3. ZDICT_trainFromBuffer_legacy() did not validate dictBuffer, samplesBuffer, or samplesSizes before reading sizes, and did not check for integer overflow before allocating the noisy guard band. 4. COVER_ctx_init() and FASTCOVER_ctx_init() calculated sample sums and advanced size pointers before validating samplesBuffer or samplesSizes. 5. ZDICT_trainFromBuffer_cover(), ZDICT_optimizeTrainFromBuffer_cover(), ZDICT_trainFromBuffer_fastCover(), and ZDICT_optimizeTrainFromBuffer_fastCover() did not validate that dictBuffer was non-NULL before proceeding with dictionary generation. Add defensive checks for NULL input pointers, add arithmetic overflow guards during sample size aggregation, and add regression tests in tests/fuzzer.c. Signed-off-by: Filippo Tedeschi --- lib/dictBuilder/cover.c | 26 ++++++++++++++++++-------- lib/dictBuilder/fastcover.c | 24 ++++++++++++++++-------- lib/dictBuilder/zdict.c | 14 ++++++++++---- tests/fuzzer.c | 5 +++++ 4 files changed, 49 insertions(+), 20 deletions(-) diff --git a/lib/dictBuilder/cover.c b/lib/dictBuilder/cover.c index 06d1cb93a5c..27a6b63b796 100644 --- a/lib/dictBuilder/cover.c +++ b/lib/dictBuilder/cover.c @@ -280,7 +280,9 @@ static COVER_ctx_t *g_coverCtx = NULL; size_t COVER_sum(const size_t *samplesSizes, unsigned nbSamples) { size_t sum = 0; unsigned i; + if (!samplesSizes) return 0; for (i = 0; i < nbSamples; ++i) { + if (samplesSizes[i] > ((size_t)-1) - sum) return (size_t)-1; sum += samplesSizes[i]; } return sum; @@ -630,14 +632,22 @@ static size_t COVER_ctx_init(COVER_ctx_t *ctx, const void *samplesBuffer, unsigned d, double splitPoint, int displayLevel) { const BYTE *const samples = (const BYTE *)samplesBuffer; - const size_t totalSamplesSize = COVER_sum(samplesSizes, nbSamples); + size_t totalSamplesSize; + unsigned nbTrainSamples; + unsigned nbTestSamples; + size_t trainingSamplesSize; + size_t testSamplesSize; + + /* Checks */ + if (!samplesBuffer || !samplesSizes) return ERROR(srcSize_wrong); + + totalSamplesSize = COVER_sum(samplesSizes, nbSamples); /* Split samples into testing and training sets */ - const unsigned nbTrainSamples = splitPoint < 1.0 ? (unsigned)((double)nbSamples * splitPoint) : nbSamples; - const unsigned nbTestSamples = splitPoint < 1.0 ? nbSamples - nbTrainSamples : nbSamples; - const size_t trainingSamplesSize = splitPoint < 1.0 ? COVER_sum(samplesSizes, nbTrainSamples) : totalSamplesSize; - const size_t testSamplesSize = splitPoint < 1.0 ? COVER_sum(samplesSizes + nbTrainSamples, nbTestSamples) : totalSamplesSize; + nbTrainSamples = splitPoint < 1.0 ? (unsigned)((double)nbSamples * splitPoint) : nbSamples; + nbTestSamples = splitPoint < 1.0 ? nbSamples - nbTrainSamples : nbSamples; + trainingSamplesSize = splitPoint < 1.0 ? COVER_sum(samplesSizes, nbTrainSamples) : totalSamplesSize; + testSamplesSize = splitPoint < 1.0 ? COVER_sum(samplesSizes + nbTrainSamples, nbTestSamples) : totalSamplesSize; ctx->displayLevel = displayLevel; - /* Checks */ if (totalSamplesSize < MAX(d, sizeof(U64)) || totalSamplesSize >= (size_t)COVER_MAX_SAMPLES_SIZE) { DISPLAYLEVEL(1, "Total samples size is too large (%u MB), maximum size is %u MB\n", @@ -825,7 +835,7 @@ ZDICTLIB_STATIC_API size_t ZDICT_trainFromBuffer_cover( DISPLAYLEVEL(1, "Cover must have at least one input file\n"); return ERROR(srcSize_wrong); } - if (dictBufferCapacity < ZDICT_DICTSIZE_MIN) { + if (dictBuffer == NULL || dictBufferCapacity < ZDICT_DICTSIZE_MIN) { DISPLAYLEVEL(1, "dictBufferCapacity must be at least %u\n", ZDICT_DICTSIZE_MIN); return ERROR(dstSize_tooSmall); @@ -1235,7 +1245,7 @@ ZDICTLIB_STATIC_API size_t ZDICT_optimizeTrainFromBuffer_cover( DISPLAYLEVEL(1, "Cover must have at least one input file\n"); return ERROR(srcSize_wrong); } - if (dictBufferCapacity < ZDICT_DICTSIZE_MIN) { + if (dictBuffer == NULL || dictBufferCapacity < ZDICT_DICTSIZE_MIN) { DISPLAYLEVEL(1, "dictBufferCapacity must be at least %u\n", ZDICT_DICTSIZE_MIN); return ERROR(dstSize_tooSmall); diff --git a/lib/dictBuilder/fastcover.c b/lib/dictBuilder/fastcover.c index 56a08138520..d5ad06d708d 100644 --- a/lib/dictBuilder/fastcover.c +++ b/lib/dictBuilder/fastcover.c @@ -311,15 +311,23 @@ FASTCOVER_ctx_init(FASTCOVER_ctx_t* ctx, int displayLevel) { const BYTE* const samples = (const BYTE*)samplesBuffer; - const size_t totalSamplesSize = COVER_sum(samplesSizes, nbSamples); + size_t totalSamplesSize; + unsigned nbTrainSamples; + unsigned nbTestSamples; + size_t trainingSamplesSize; + size_t testSamplesSize; + + /* Checks */ + if (!samplesBuffer || !samplesSizes) return ERROR(srcSize_wrong); + + totalSamplesSize = COVER_sum(samplesSizes, nbSamples); /* Split samples into testing and training sets */ - const unsigned nbTrainSamples = splitPoint < 1.0 ? (unsigned)((double)nbSamples * splitPoint) : nbSamples; - const unsigned nbTestSamples = splitPoint < 1.0 ? nbSamples - nbTrainSamples : nbSamples; - const size_t trainingSamplesSize = splitPoint < 1.0 ? COVER_sum(samplesSizes, nbTrainSamples) : totalSamplesSize; - const size_t testSamplesSize = splitPoint < 1.0 ? COVER_sum(samplesSizes + nbTrainSamples, nbTestSamples) : totalSamplesSize; + nbTrainSamples = splitPoint < 1.0 ? (unsigned)((double)nbSamples * splitPoint) : nbSamples; + nbTestSamples = splitPoint < 1.0 ? nbSamples - nbTrainSamples : nbSamples; + trainingSamplesSize = splitPoint < 1.0 ? COVER_sum(samplesSizes, nbTrainSamples) : totalSamplesSize; + testSamplesSize = splitPoint < 1.0 ? COVER_sum(samplesSizes + nbTrainSamples, nbTestSamples) : totalSamplesSize; ctx->displayLevel = displayLevel; - /* Checks */ if (totalSamplesSize < MAX(d, sizeof(U64)) || totalSamplesSize >= (size_t)FASTCOVER_MAX_SAMPLES_SIZE) { DISPLAYLEVEL(1, "Total samples size is too large (%u MB), maximum size is %u MB\n", @@ -572,7 +580,7 @@ ZDICT_trainFromBuffer_fastCover(void* dictBuffer, size_t dictBufferCapacity, DISPLAYLEVEL(1, "FASTCOVER must have at least one input file\n"); return ERROR(srcSize_wrong); } - if (dictBufferCapacity < ZDICT_DICTSIZE_MIN) { + if (dictBuffer == NULL || dictBufferCapacity < ZDICT_DICTSIZE_MIN) { DISPLAYLEVEL(1, "dictBufferCapacity must be at least %u\n", ZDICT_DICTSIZE_MIN); return ERROR(dstSize_tooSmall); @@ -662,7 +670,7 @@ ZDICT_optimizeTrainFromBuffer_fastCover( DISPLAYLEVEL(1, "FASTCOVER must have at least one input file\n"); return ERROR(srcSize_wrong); } - if (dictBufferCapacity < ZDICT_DICTSIZE_MIN) { + if (dictBuffer == NULL || dictBufferCapacity < ZDICT_DICTSIZE_MIN) { DISPLAYLEVEL(1, "dictBufferCapacity must be at least %u\n", ZDICT_DICTSIZE_MIN); return ERROR(dstSize_tooSmall); diff --git a/lib/dictBuilder/zdict.c b/lib/dictBuilder/zdict.c index befa616b87f..0e810b9db3b 100644 --- a/lib/dictBuilder/zdict.c +++ b/lib/dictBuilder/zdict.c @@ -101,7 +101,7 @@ const char* ZDICT_getErrorName(size_t errorCode) { return ERR_getErrorName(error unsigned ZDICT_getDictID(const void* dictBuffer, size_t dictSize) { - if (dictSize < 8) return 0; + if (dictBuffer == NULL || dictSize < 8) return 0; if (MEM_readLE32(dictBuffer) != ZSTD_MAGIC_DICTIONARY) return 0; return MEM_readLE32((const char*)dictBuffer + 4); } @@ -109,7 +109,7 @@ unsigned ZDICT_getDictID(const void* dictBuffer, size_t dictSize) size_t ZDICT_getDictHeaderSize(const void* dictBuffer, size_t dictSize) { size_t headerSize; - if (dictSize <= 8 || MEM_readLE32(dictBuffer) != ZSTD_MAGIC_DICTIONARY) return ERROR(dictionary_corrupted); + if (dictBuffer == NULL || dictSize <= 8 || MEM_readLE32(dictBuffer) != ZSTD_MAGIC_DICTIONARY) return ERROR(dictionary_corrupted); { ZSTD_compressedBlockState_t* bs = (ZSTD_compressedBlockState_t*)malloc(sizeof(ZSTD_compressedBlockState_t)); U32* wksp = (U32*)malloc(HUF_WORKSPACE_SIZE); @@ -624,9 +624,13 @@ static void ZDICT_countEStats(EStats_ress_t esr, const ZSTD_parameters* params, static size_t ZDICT_totalSampleSize(const size_t* fileSizes, unsigned nbFiles) { - size_t total=0; + size_t total = 0; unsigned u; - for (u=0; u ((size_t)-1) - total) return (size_t)-1; + total += fileSizes[u]; + } return total; } @@ -1092,7 +1096,9 @@ size_t ZDICT_trainFromBuffer_legacy(void* dictBuffer, size_t dictBufferCapacity, size_t result; void* newBuff; size_t const sBuffSize = ZDICT_totalSampleSize(samplesSizes, nbSamples); + if (!dictBuffer || !samplesBuffer || !samplesSizes) return 0; if (sBuffSize < ZDICT_MIN_SAMPLES_SIZE) return 0; /* not enough content => no dictionary */ + if (sBuffSize > ((size_t)-1) - NOISELENGTH) return ERROR(memory_allocation); newBuff = malloc(sBuffSize + NOISELENGTH); if (!newBuff) return ERROR(memory_allocation); diff --git a/tests/fuzzer.c b/tests/fuzzer.c index 32360b9a58d..2aca6554940 100644 --- a/tests/fuzzer.c +++ b/tests/fuzzer.c @@ -3136,6 +3136,11 @@ static int basicUnitTests(U32 const seed, double compressibility) } DISPLAYLEVEL(3, "OK : %u \n", (unsigned)dictHeaderSize); + DISPLAYLEVEL(3, "test%3i : check dictID and headerSize on NULL buffer : ", testNb++); + if (ZDICT_getDictID(NULL, 100) != 0) goto _output_error; + if (!ZDICT_isError(ZDICT_getDictHeaderSize(NULL, 100))) goto _output_error; + DISPLAYLEVEL(3, "OK \n"); + DISPLAYLEVEL(3, "test%3i : compress with dictionary : ", testNb++); cSize = ZSTD_compress_usingDict(cctx, compressedBuffer, compressedBufferSize, CNBuffer, CNBuffSize,