From 7d777bbfe2613640ba9517d8af2fbbd574c2fa24 Mon Sep 17 00:00:00 2001 From: nileshpatil6 Date: Fri, 21 Aug 2026 12:13:21 +0530 Subject: [PATCH 1/4] Reject a too large block in ZSTD_compressSequencesAndLiterals() ZSTD_compressSequences() validates every block through determine_blockSize(), which in explicit-delimiter mode rejects a block whose regenerated size exceeds cctx->blockSizeMax: if (explicitBlockSize > blockSize) RETURN_ERROR(externalSequences_invalid, "sequences incorrectly define a too large block"); ZSTD_compressSequencesAndLiterals_internal() only supports explicit delimiters, but does not perform that check. It calls ZSTD_get1BlockSummary() and validates block.litSize against litSize only, so sequences describing an over-sized block are accepted and the function returns success while emitting a block that exceeds the maximum block size the format allows. The two APIs disagree on identical input: ZSTD_compressSequences -> External sequences are not valid ZSTD_compressSequencesAndLiterals -> SUCCESS (cSize = 36) For a large enough block the resulting frame is not decodable, so the caller receives a success return for bytes that cannot be read back. The guard was added for the sibling path in 87dcd33 (2022). The AndLiterals pipeline was split out later, in 14a21e4 (2024), without carrying it over. --- lib/compress/zstd_compress.c | 1 + tests/fuzzer.c | 43 ++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index c06f2e1bb98..52c00d275aa 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -8033,6 +8033,7 @@ ZSTD_compressSequencesAndLiterals_internal(ZSTD_CCtx* cctx, FORWARD_IF_ERROR(block.nbSequences, "Error while trying to determine nb of sequences for a block"); assert(block.nbSequences <= nbSequences); RETURN_ERROR_IF(block.litSize > litSize, externalSequences_invalid, "discrepancy: Sequences require more literals than present in buffer"); + RETURN_ERROR_IF(block.blockSize > cctx->blockSizeMax, externalSequences_invalid, "sequences incorrectly define a too large block"); ZSTD_resetSeqStore(&cctx->seqStore); conversionStatus = ZSTD_convertBlockSequences(cctx, diff --git a/tests/fuzzer.c b/tests/fuzzer.c index 7b7c9d66631..3ff1d0fa1c9 100644 --- a/tests/fuzzer.c +++ b/tests/fuzzer.c @@ -4365,6 +4365,49 @@ static int basicUnitTests(U32 const seed, double compressibility) } DISPLAYLEVEL(3, "OK \n"); + DISPLAYLEVEL(3, "test%3i : ZSTD_compressSequencesAndLiterals : reject a too large block : ", testNb++); + { + /* A single sequence, followed by an explicit block delimiter, describing + * one block whose regenerated size exceeds ZSTD_BLOCKSIZE_MAX. + * ZSTD_compressSequences() already rejects this in determine_blockSize(), + * so the AndLiterals() variant must reject it too. Otherwise it reports + * success while emitting a block that violates the format. */ + const size_t litLen = 16; + const size_t blockSize = ZSTD_BLOCKSIZE_MAX + 1; + BYTE* const dst = (BYTE*)compressedBuffer; + size_t const dstCapacity = ZSTD_compressBound(blockSize); + BYTE litBuffer[32]; + ZSTD_Sequence seqs[2]; + ZSTD_CCtx* const cctx = ZSTD_createCCtx(); + size_t cSize; + size_t i; + + assert(cctx != NULL); + for (i = 0; i < sizeof(litBuffer); i++) litBuffer[i] = (BYTE)('a' + (i % 3)); + + seqs[0].offset = 1; + seqs[0].litLength = (U32)litLen; + seqs[0].matchLength = (U32)(blockSize - litLen); + seqs[0].rep = 0; + seqs[1].offset = 0; /* block delimiter */ + seqs[1].litLength = 0; + seqs[1].matchLength = 0; + seqs[1].rep = 0; + + CHECK_Z(ZSTD_CCtx_setParameter(cctx, ZSTD_c_blockDelimiters, ZSTD_sf_explicitBlockDelimiters)); + cSize = ZSTD_compressSequencesAndLiterals(cctx, dst, dstCapacity, + seqs, 2, + litBuffer, litLen, sizeof(litBuffer), + blockSize); + if (!ZSTD_isError(cSize)) { + DISPLAY("ZSTD_compressSequencesAndLiterals() should have failed: sequences define a block larger than ZSTD_BLOCKSIZE_MAX\n"); + ZSTD_freeCCtx(cctx); + goto _output_error; + } + ZSTD_freeCCtx(cctx); + } + DISPLAYLEVEL(3, "OK \n"); + /* Multiple blocks of zeros test */ #define LONGZEROSLENGTH 1000000 /* 1MB of zeros */ DISPLAYLEVEL(3, "test%3i : compress %u zeroes : ", testNb++, LONGZEROSLENGTH); From bf7504bf1a65e48faa14f0293b1a961a81534b8e Mon Sep 17 00:00:00 2001 From: nileshpatil6 Date: Sat, 22 Aug 2026 21:55:33 +0530 Subject: [PATCH 2/4] tests: rename cSize in the new test to avoid shadowing basicUnitTests() already declares size_t cSize, so the inner declaration tripped -Wshadow and failed builds using MOREFLAGS=-Werror. --- tests/fuzzer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/fuzzer.c b/tests/fuzzer.c index 3ff1d0fa1c9..2fd92a54ec7 100644 --- a/tests/fuzzer.c +++ b/tests/fuzzer.c @@ -4379,7 +4379,7 @@ static int basicUnitTests(U32 const seed, double compressibility) BYTE litBuffer[32]; ZSTD_Sequence seqs[2]; ZSTD_CCtx* const cctx = ZSTD_createCCtx(); - size_t cSize; + size_t cSizeTooLarge; size_t i; assert(cctx != NULL); @@ -4395,11 +4395,11 @@ static int basicUnitTests(U32 const seed, double compressibility) seqs[1].rep = 0; CHECK_Z(ZSTD_CCtx_setParameter(cctx, ZSTD_c_blockDelimiters, ZSTD_sf_explicitBlockDelimiters)); - cSize = ZSTD_compressSequencesAndLiterals(cctx, dst, dstCapacity, + cSizeTooLarge = ZSTD_compressSequencesAndLiterals(cctx, dst, dstCapacity, seqs, 2, litBuffer, litLen, sizeof(litBuffer), blockSize); - if (!ZSTD_isError(cSize)) { + if (!ZSTD_isError(cSizeTooLarge)) { DISPLAY("ZSTD_compressSequencesAndLiterals() should have failed: sequences define a block larger than ZSTD_BLOCKSIZE_MAX\n"); ZSTD_freeCCtx(cctx); goto _output_error; From 7bc5369666c4a0be663aba78890c3dc6f7f6f102 Mon Sep 17 00:00:00 2001 From: Yann Collet Date: Tue, 10 Nov 2026 14:05:16 -0800 Subject: [PATCH 3/4] tests: shorten the too large block test Same case in 12 lines instead of 43: the sequences are initialized in their declaration, the literals come from CNBuffer, and CHECK() replaces the error branch. --- tests/fuzzer.c | 43 ++++++------------------------------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/tests/fuzzer.c b/tests/fuzzer.c index 2fd92a54ec7..8d1070778dd 100644 --- a/tests/fuzzer.c +++ b/tests/fuzzer.c @@ -4366,44 +4366,13 @@ static int basicUnitTests(U32 const seed, double compressibility) DISPLAYLEVEL(3, "OK \n"); DISPLAYLEVEL(3, "test%3i : ZSTD_compressSequencesAndLiterals : reject a too large block : ", testNb++); - { - /* A single sequence, followed by an explicit block delimiter, describing - * one block whose regenerated size exceeds ZSTD_BLOCKSIZE_MAX. - * ZSTD_compressSequences() already rejects this in determine_blockSize(), - * so the AndLiterals() variant must reject it too. Otherwise it reports - * success while emitting a block that violates the format. */ - const size_t litLen = 16; - const size_t blockSize = ZSTD_BLOCKSIZE_MAX + 1; - BYTE* const dst = (BYTE*)compressedBuffer; - size_t const dstCapacity = ZSTD_compressBound(blockSize); - BYTE litBuffer[32]; - ZSTD_Sequence seqs[2]; + { /* one block of ZSTD_BLOCKSIZE_MAX + 1 bytes : 16 literals and a match, then the block delimiter */ + ZSTD_Sequence const seqs[2] = { { 1, 16, ZSTD_BLOCKSIZE_MAX + 1 - 16, 0 }, { 0, 0, 0, 0 } }; ZSTD_CCtx* const cctx = ZSTD_createCCtx(); - size_t cSizeTooLarge; - size_t i; - - assert(cctx != NULL); - for (i = 0; i < sizeof(litBuffer); i++) litBuffer[i] = (BYTE)('a' + (i % 3)); - - seqs[0].offset = 1; - seqs[0].litLength = (U32)litLen; - seqs[0].matchLength = (U32)(blockSize - litLen); - seqs[0].rep = 0; - seqs[1].offset = 0; /* block delimiter */ - seqs[1].litLength = 0; - seqs[1].matchLength = 0; - seqs[1].rep = 0; - - CHECK_Z(ZSTD_CCtx_setParameter(cctx, ZSTD_c_blockDelimiters, ZSTD_sf_explicitBlockDelimiters)); - cSizeTooLarge = ZSTD_compressSequencesAndLiterals(cctx, dst, dstCapacity, - seqs, 2, - litBuffer, litLen, sizeof(litBuffer), - blockSize); - if (!ZSTD_isError(cSizeTooLarge)) { - DISPLAY("ZSTD_compressSequencesAndLiterals() should have failed: sequences define a block larger than ZSTD_BLOCKSIZE_MAX\n"); - ZSTD_freeCCtx(cctx); - goto _output_error; - } + CHECK(cctx != NULL); + CHECK_Z( ZSTD_CCtx_setParameter(cctx, ZSTD_c_blockDelimiters, ZSTD_sf_explicitBlockDelimiters) ); + CHECK( ZSTD_isError(ZSTD_compressSequencesAndLiterals(cctx, compressedBuffer, compressedBufferSize, + seqs, 2, CNBuffer, 16, CNBuffSize, ZSTD_BLOCKSIZE_MAX + 1)) ); ZSTD_freeCCtx(cctx); } DISPLAYLEVEL(3, "OK \n"); From 918ade2db2e2b035bf5d773b316c313889a4fea5 Mon Sep 17 00:00:00 2001 From: Yann Collet Date: Tue, 10 Nov 2026 14:05:23 -0800 Subject: [PATCH 4/4] zstd.h: state the block size limit of ZSTD_compressSequencesAndLiterals() The caller delimits the blocks, and a block larger than the maximum block size now generates an error. --- lib/zstd.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/zstd.h b/lib/zstd.h index 97fef316fd6..3e7ef796dac 100644 --- a/lib/zstd.h +++ b/lib/zstd.h @@ -1699,6 +1699,8 @@ ZSTD_compressSequences(ZSTD_CCtx* cctx, * - @litBufCapacity is the size of the underlying buffer into which literals are written, starting at address @literals. * @litBufCapacity must be at least 8 bytes larger than @litSize. * - @decompressedSize must be correct, and correspond to the sum of all Sequences. Any discrepancy will generate an error. + * - Each block must regenerate at most ZSTD_BLOCKSIZE_MAX bytes, or less with a smaller window or ZSTD_c_maxBlockSize. + * A larger block will generate an error. * @return : final compressed size, or a ZSTD error code. */ ZSTDLIB_STATIC_API size_t