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] 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