diff --git a/lib/common/error_private.c b/lib/common/error_private.c index fb4d7059621..8b8e7a047d9 100644 --- a/lib/common/error_private.c +++ b/lib/common/error_private.c @@ -53,6 +53,7 @@ const char* ERR_getErrorString(ERR_enum code) case PREFIX(dstBuffer_wrong): return "Destination buffer is wrong"; case PREFIX(srcBuffer_wrong): return "Source buffer is wrong"; case PREFIX(externalMatchFinder_failed): return "External matchfinder returned an error code"; + case PREFIX(externalSequences_invalid): return "External sequences are not valid"; case PREFIX(maxCode): default: return notErrorCode; } diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 3a48e7dcd48..f74a0883ebe 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -6156,8 +6156,8 @@ size_t ZSTD_compress2(ZSTD_CCtx* cctx, * @returns a ZSTD error code if sequence is not valid */ static size_t -ZSTD_validateSequence(U32 offCode, U32 matchLength, - size_t posInSrc, U32 windowLog, size_t dictSize) +ZSTD_validateSequence(U32 offCode, U32 matchLength, U32 minMatch, + size_t posInSrc, U32 windowLog, size_t dictSize, int useExternalMatchFinder) { U32 const windowSize = 1u << windowLog; /* posInSrc represents the amount of data the decoder would decode up to this point. @@ -6166,8 +6166,10 @@ ZSTD_validateSequence(U32 offCode, U32 matchLength, * window size. After output surpasses windowSize, we're limited to windowSize offsets again. */ size_t const offsetBound = posInSrc > windowSize ? (size_t)windowSize : posInSrc + (size_t)dictSize; - RETURN_ERROR_IF(offCode > OFFSET_TO_OFFBASE(offsetBound), corruption_detected, "Offset too large!"); - RETURN_ERROR_IF(matchLength < MINMATCH, corruption_detected, "Matchlength too small"); + size_t const matchLenLowerBound = (minMatch == 3 || useExternalMatchFinder) ? 3 : 4; + RETURN_ERROR_IF(offCode > OFFSET_TO_OFFBASE(offsetBound), externalSequences_invalid, "Offset too large!"); + /* Validate maxNbSeq is large enough for the given matchLength and minMatch */ + RETURN_ERROR_IF(matchLength < matchLenLowerBound, externalSequences_invalid, "Matchlength too small for the minMatch"); return 0; } @@ -6220,11 +6222,11 @@ ZSTD_copySequencesToSeqStoreExplicitBlockDelim(ZSTD_CCtx* cctx, DEBUGLOG(6, "Storing sequence: (of: %u, ml: %u, ll: %u)", offBase, matchLength, litLength); if (cctx->appliedParams.validateSequences) { seqPos->posInSrc += litLength + matchLength; - FORWARD_IF_ERROR(ZSTD_validateSequence(offBase, matchLength, seqPos->posInSrc, - cctx->appliedParams.cParams.windowLog, dictSize), + FORWARD_IF_ERROR(ZSTD_validateSequence(offBase, matchLength, cctx->appliedParams.cParams.minMatch, seqPos->posInSrc, + cctx->appliedParams.cParams.windowLog, dictSize, cctx->appliedParams.useExternalMatchFinder), "Sequence validation failed"); } - RETURN_ERROR_IF(idx - seqPos->idx > cctx->seqStore.maxNbSeq, memory_allocation, + RETURN_ERROR_IF(idx - seqPos->idx >= cctx->seqStore.maxNbSeq, externalSequences_invalid, "Not enough memory allocated. Try adjusting ZSTD_c_minMatch."); ZSTD_storeSeq(&cctx->seqStore, litLength, ip, iend, offBase, matchLength); ip += matchLength + litLength; @@ -6332,12 +6334,12 @@ ZSTD_copySequencesToSeqStoreNoBlockDelim(ZSTD_CCtx* cctx, ZSTD_sequencePosition* if (cctx->appliedParams.validateSequences) { seqPos->posInSrc += litLength + matchLength; - FORWARD_IF_ERROR(ZSTD_validateSequence(offBase, matchLength, seqPos->posInSrc, - cctx->appliedParams.cParams.windowLog, dictSize), + FORWARD_IF_ERROR(ZSTD_validateSequence(offBase, matchLength, cctx->appliedParams.cParams.minMatch, seqPos->posInSrc, + cctx->appliedParams.cParams.windowLog, dictSize, cctx->appliedParams.useExternalMatchFinder), "Sequence validation failed"); } DEBUGLOG(6, "Storing sequence: (of: %u, ml: %u, ll: %u)", offBase, matchLength, litLength); - RETURN_ERROR_IF(idx - seqPos->idx > cctx->seqStore.maxNbSeq, memory_allocation, + RETURN_ERROR_IF(idx - seqPos->idx >= cctx->seqStore.maxNbSeq, externalSequences_invalid, "Not enough memory allocated. Try adjusting ZSTD_c_minMatch."); ZSTD_storeSeq(&cctx->seqStore, litLength, ip, iend, offBase, matchLength); ip += matchLength + litLength; diff --git a/lib/zstd_errors.h b/lib/zstd_errors.h index bd6dbee5ff8..0ad34fe2e72 100644 --- a/lib/zstd_errors.h +++ b/lib/zstd_errors.h @@ -94,6 +94,7 @@ typedef enum { ZSTD_error_dstBuffer_wrong = 104, ZSTD_error_srcBuffer_wrong = 105, ZSTD_error_externalMatchFinder_failed = 106, + ZSTD_error_externalSequences_invalid = 107, ZSTD_error_maxCode = 120 /* never EVER use this value directly, it can change in future versions! Use ZSTD_isError() instead */ } ZSTD_ErrorCode; diff --git a/tests/zstreamtest.c b/tests/zstreamtest.c index 4a621692dcd..3a3faa33731 100644 --- a/tests/zstreamtest.c +++ b/tests/zstreamtest.c @@ -2066,6 +2066,107 @@ static int basicUnitTests(U32 seed, double compressibility, int bigTests) } DISPLAYLEVEL(3, "OK \n"); + /* Test Sequence Validation */ + DISPLAYLEVEL(3, "test%3i : Testing sequence validation: ", testNb++); + { + ZSTD_CCtx* cctx = ZSTD_createCCtx(); + + /* Test minMatch >= 4, matchLength < 4 */ + { + size_t srcSize = 11; + void* const src = CNBuffer; + size_t dstSize = ZSTD_compressBound(srcSize); + void* const dst = compressedBuffer; + size_t const kNbSequences = 4; + ZSTD_Sequence* sequences = malloc(sizeof(ZSTD_Sequence) * kNbSequences); + + memset(src, 'x', srcSize); + + sequences[0] = (ZSTD_Sequence) {1, 1, 3, 0}; + sequences[1] = (ZSTD_Sequence) {1, 0, 3, 0}; + sequences[2] = (ZSTD_Sequence) {1, 0, 3, 0}; + sequences[3] = (ZSTD_Sequence) {0, 1, 0, 0}; + + /* Test with sequence validation */ + CHECK_Z(ZSTD_CCtx_setParameter(cctx, ZSTD_c_minMatch, 5)); + CHECK_Z(ZSTD_CCtx_setParameter(cctx, ZSTD_c_blockDelimiters, ZSTD_sf_explicitBlockDelimiters)); + CHECK_Z(ZSTD_CCtx_setParameter(cctx, ZSTD_c_validateSequences, 1)); + + cSize = ZSTD_compressSequences(cctx, dst, dstSize, + sequences, kNbSequences, + src, srcSize); + + CHECK(!ZSTD_isError(cSize), "Should throw an error"); /* maxNbSeq is too small and an assert will fail */ + CHECK(ZSTD_getErrorCode(cSize) != ZSTD_error_externalSequences_invalid, "Wrong error code: %s", ZSTD_getErrorName(cSize)); /* fails sequence validation */ + + ZSTD_CCtx_reset(cctx, ZSTD_reset_session_and_parameters); + + /* Test without sequence validation */ + CHECK_Z(ZSTD_CCtx_setParameter(cctx, ZSTD_c_minMatch, 5)); + CHECK_Z(ZSTD_CCtx_setParameter(cctx, ZSTD_c_blockDelimiters, ZSTD_sf_explicitBlockDelimiters)); + CHECK_Z(ZSTD_CCtx_setParameter(cctx, ZSTD_c_validateSequences, 0)); + + cSize = ZSTD_compressSequences(cctx, dst, dstSize, + sequences, kNbSequences, + src, srcSize); + + CHECK(!ZSTD_isError(cSize), "Should throw an error"); /* maxNbSeq is too small and an assert will fail */ + CHECK(ZSTD_getErrorCode(cSize) != ZSTD_error_externalSequences_invalid, "Wrong error code: %s", ZSTD_getErrorName(cSize)); /* fails sequence validation */ + + free(sequences); + } + + ZSTD_CCtx_reset(cctx, ZSTD_reset_session_and_parameters); + + { /* Test case with two additional sequences */ + size_t srcSize = 19; + void* const src = CNBuffer; + size_t dstSize = ZSTD_compressBound(srcSize); + void* const dst = compressedBuffer; + size_t const kNbSequences = 7; + ZSTD_Sequence* sequences = malloc(sizeof(ZSTD_Sequence) * kNbSequences); + + memset(src, 'x', srcSize); + + sequences[0] = (ZSTD_Sequence) {1, 1, 3, 0}; + sequences[1] = (ZSTD_Sequence) {1, 0, 3, 0}; + sequences[2] = (ZSTD_Sequence) {1, 0, 3, 0}; + sequences[3] = (ZSTD_Sequence) {1, 0, 3, 0}; + sequences[4] = (ZSTD_Sequence) {1, 0, 3, 0}; + sequences[5] = (ZSTD_Sequence) {1, 0, 3, 0}; + sequences[6] = (ZSTD_Sequence) {0, 0, 0, 0}; + + CHECK_Z(ZSTD_CCtx_setParameter(cctx, ZSTD_c_minMatch, 5)); + CHECK_Z(ZSTD_CCtx_setParameter(cctx, ZSTD_c_blockDelimiters, ZSTD_sf_explicitBlockDelimiters)); + CHECK_Z(ZSTD_CCtx_setParameter(cctx, ZSTD_c_validateSequences, 1)); + + cSize = ZSTD_compressSequences(cctx, dst, dstSize, + sequences, kNbSequences, + src, srcSize); + + CHECK(!ZSTD_isError(cSize), "Should throw an error"); /* maxNbSeq is too small and an assert will fail */ + CHECK(ZSTD_getErrorCode(cSize) != ZSTD_error_externalSequences_invalid, "Wrong error code: %s", ZSTD_getErrorName(cSize)); /* fails sequence validation */ + + ZSTD_CCtx_reset(cctx, ZSTD_reset_session_and_parameters); + + /* Test without sequence validation */ + CHECK_Z(ZSTD_CCtx_setParameter(cctx, ZSTD_c_minMatch, 5)); + CHECK_Z(ZSTD_CCtx_setParameter(cctx, ZSTD_c_blockDelimiters, ZSTD_sf_explicitBlockDelimiters)); + CHECK_Z(ZSTD_CCtx_setParameter(cctx, ZSTD_c_validateSequences, 0)); + + cSize = ZSTD_compressSequences(cctx, dst, dstSize, + sequences, kNbSequences, + src, srcSize); + + CHECK(!ZSTD_isError(cSize), "Should throw an error"); /* maxNbSeq is too small and an assert will fail */ + CHECK(ZSTD_getErrorCode(cSize) != ZSTD_error_externalSequences_invalid, "Wrong error code: %s", ZSTD_getErrorName(cSize)); /* fails sequence validation */ + + free(sequences); + } + ZSTD_freeCCtx(cctx); + } + DISPLAYLEVEL(3, "OK \n"); + _end: FUZ_freeDictionary(dictionary); ZSTD_freeCStream(zc);