From 51cab980abaf367922a8af866aae948aeca4694b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ey=C3=BCp=20Can=20Akman?= Date: Mon, 13 Jul 2026 18:08:45 +0300 Subject: [PATCH 1/2] Fix out-of-bounds read in FASTCOVER on small training samples When every training sample is smaller than the dmer size, splitPoint < 1 can shrink the training set below MAX(d, 8) after the total samples size check passes, so nbDmers underflows and FASTCOVER reads past the sample buffer (#4499). It now returns srcSize_wrong. Adds a unit test in basicUnitTests. The same underflow in COVER_ctx_init is addressed separately in #4690. --- lib/dictBuilder/fastcover.c | 6 ++++++ tests/fuzzer.c | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/dictBuilder/fastcover.c b/lib/dictBuilder/fastcover.c index 56a08138520..3d8e4cbaedf 100644 --- a/lib/dictBuilder/fastcover.c +++ b/lib/dictBuilder/fastcover.c @@ -339,6 +339,12 @@ FASTCOVER_ctx_init(FASTCOVER_ctx_t* ctx, return ERROR(srcSize_wrong); } + /* Check the training set can hold at least one dmer, otherwise nbDmers underflows below */ + if (trainingSamplesSize < MAX(d, sizeof(U64))) { + DISPLAYLEVEL(1, "Total training samples size is %u and is invalid.\n", (unsigned)trainingSamplesSize); + return ERROR(srcSize_wrong); + } + /* Zero the context */ memset(ctx, 0, sizeof(*ctx)); DISPLAYLEVEL(2, "Training on %u samples of total size %u\n", nbTrainSamples, diff --git a/tests/fuzzer.c b/tests/fuzzer.c index 7b7c9d66631..43850a9d7b5 100644 --- a/tests/fuzzer.c +++ b/tests/fuzzer.c @@ -3060,6 +3060,16 @@ static int basicUnitTests(U32 const seed, double compressibility) if (ZDICT_isError(dictSize)) goto _output_error; DISPLAYLEVEL(3, "OK, created dictionary of size %u \n", (unsigned)dictSize); + DISPLAYLEVEL(3, "test%3i : FASTCOVER dictBuilder samples smaller than a dmer (issue #4499) : ", testNb++); + { const char smallData[8] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H' }; + const size_t smallSizes[8] = { 1, 1, 1, 1, 1, 1, 1, 1 }; + size_t const r = ZDICT_trainFromBuffer(dictBuffer, dictBufferCapacity, + smallData, smallSizes, 8); + if (!ZDICT_isError(r)) goto _output_error; + if (ZSTD_getErrorCode(r) != ZSTD_error_srcSize_wrong) goto _output_error; + } + DISPLAYLEVEL(3, "OK \n"); + DISPLAYLEVEL(3, "test%3i : Multithreaded COVER dictBuilder : ", testNb++); { U32 u; for (u=0; u Date: Fri, 6 Nov 2026 11:41:59 +0300 Subject: [PATCH 2/2] Name the FASTCOVER dmer read length --- lib/dictBuilder/fastcover.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/dictBuilder/fastcover.c b/lib/dictBuilder/fastcover.c index 3d8e4cbaedf..f59170ece60 100644 --- a/lib/dictBuilder/fastcover.c +++ b/lib/dictBuilder/fastcover.c @@ -317,10 +317,11 @@ FASTCOVER_ctx_init(FASTCOVER_ctx_t* ctx, 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; + const size_t readLength = MAX(d, sizeof(U64)); ctx->displayLevel = displayLevel; /* Checks */ - if (totalSamplesSize < MAX(d, sizeof(U64)) || + if (totalSamplesSize < readLength || totalSamplesSize >= (size_t)FASTCOVER_MAX_SAMPLES_SIZE) { DISPLAYLEVEL(1, "Total samples size is too large (%u MB), maximum size is %u MB\n", (unsigned)(totalSamplesSize >> 20), (FASTCOVER_MAX_SAMPLES_SIZE >> 20)); @@ -340,7 +341,7 @@ FASTCOVER_ctx_init(FASTCOVER_ctx_t* ctx, } /* Check the training set can hold at least one dmer, otherwise nbDmers underflows below */ - if (trainingSamplesSize < MAX(d, sizeof(U64))) { + if (trainingSamplesSize < readLength) { DISPLAYLEVEL(1, "Total training samples size is %u and is invalid.\n", (unsigned)trainingSamplesSize); return ERROR(srcSize_wrong); } @@ -357,7 +358,7 @@ FASTCOVER_ctx_init(FASTCOVER_ctx_t* ctx, ctx->nbSamples = nbSamples; ctx->nbTrainSamples = nbTrainSamples; ctx->nbTestSamples = nbTestSamples; - ctx->nbDmers = trainingSamplesSize - MAX(d, sizeof(U64)) + 1; + ctx->nbDmers = trainingSamplesSize - readLength + 1; ctx->d = d; ctx->f = f; ctx->accelParams = accelParams;