diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 11dbf05c5..ae9898f64 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -265,6 +265,9 @@ size_t ZSTD_rust_blockSizeExplicitDelimiter(const ZSTD_Sequence* inSeqs, size_t ZSTD_rust_determineBlockSize(int mode, size_t blockSize, size_t remaining, const ZSTD_Sequence* inSeqs, size_t inSeqsSize, U32 seqIdx); +size_t ZSTD_rust_validateSequence(U32 offBase, U32 matchLength, U32 minMatch, + size_t posInSrc, U32 windowLog, size_t dictSize, + int useSequenceProducer); typedef char ZSTD_rust_stats_seqdef_layout[(sizeof(SeqDef) == 8) ? 1 : -1]; typedef char ZSTD_rust_stats_seqstore_long_length_pos[ @@ -5339,18 +5342,9 @@ static size_t ZSTD_validateSequence(U32 offBase, U32 matchLength, U32 minMatch, size_t posInSrc, U32 windowLog, size_t dictSize, int useSequenceProducer) { - U32 const windowSize = 1u << windowLog; - /* posInSrc represents the amount of data the decoder would decode up to this point. - * As long as the amount of data decoded is less than or equal to window size, offsets may be - * larger than the total length of output decoded in order to reference the dict, even larger than - * 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; - size_t const matchLenLowerBound = (minMatch == 3 || useSequenceProducer) ? 3 : 4; - RETURN_ERROR_IF(offBase > 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; + return ZSTD_rust_validateSequence(offBase, matchLength, minMatch, + posInSrc, windowLog, dictSize, + useSequenceProducer); } /* Returns an offset code, given a sequence's raw offset, the ongoing repcode array, and whether litLength == 0 */ diff --git a/rust/src/zstd_compress_stats.rs b/rust/src/zstd_compress_stats.rs index 1ba8ef6bd..96eaf6bff 100644 --- a/rust/src/zstd_compress_stats.rs +++ b/rust/src/zstd_compress_stats.rs @@ -184,6 +184,46 @@ pub unsafe extern "C" fn ZSTD_rust_finalizeOffBase( off_base } +/// Validates one external sequence against the decoder window and match-size +/// rules. This is the Rust leaf for C's `ZSTD_validateSequence()`. +#[allow(clippy::too_many_arguments)] +#[no_mangle] +pub extern "C" fn ZSTD_rust_validateSequence( + off_base: u32, + match_length: u32, + min_match: u32, + pos_in_src: usize, + window_log: u32, + dict_size: usize, + use_sequence_producer: c_int, +) -> usize { + let window_size = 1u32 << window_log; + /* As long as the decoded position is within the window, the dictionary + * can extend the largest valid offset. Once it is past the window, the + * offset is limited to the window size itself. */ + let offset_bound = if pos_in_src > window_size as usize { + window_size as usize + } else { + pos_in_src.wrapping_add(dict_size) + }; + debug_assert!(offset_bound > 0); + + let offset_bound_off_base = offset_bound.wrapping_add(ZSTD_REP_NUM); + if off_base as usize > offset_bound_off_base { + return ERROR(ZstdErrorCode::ExternalSequencesInvalid); + } + + let match_len_lower_bound = if min_match == 3 || use_sequence_producer != 0 { + 3 + } else { + 4 + }; + if match_length < match_len_lower_bound { + return ERROR(ZstdErrorCode::ExternalSequencesInvalid); + } + 0 +} + /// Finds the next explicit block delimiter and returns the represented size. /// /// The scan is half-open at `inSeqsSize`: a delimiter at the final element is @@ -2410,6 +2450,36 @@ mod tests { assert_eq!(finalize(31, 0), 34); // ordinary raw offset } + #[test] + fn validate_sequence_accepts_valid_input_and_caps_at_window_size() { + let result = ZSTD_rust_validateSequence(1027, 4, 4, 2048, 10, 99, 0); + assert_eq!(result, 0); + } + + #[test] + fn validate_sequence_rejects_oversized_offset() { + let result = ZSTD_rust_validateSequence(1028, 4, 4, 2048, 10, 99, 0); + assert_eq!(result, ERROR(ZstdErrorCode::ExternalSequencesInvalid)); + } + + #[test] + fn validate_sequence_rejects_too_short_match() { + let result = ZSTD_rust_validateSequence(103, 3, 4, 100, 10, 0, 0); + assert_eq!(result, ERROR(ZstdErrorCode::ExternalSequencesInvalid)); + } + + #[test] + fn validate_sequence_accepts_three_byte_match_for_min_match_three() { + let result = ZSTD_rust_validateSequence(103, 3, 3, 100, 10, 0, 0); + assert_eq!(result, 0); + } + + #[test] + fn validate_sequence_accepts_three_byte_match_from_sequence_producer() { + let result = ZSTD_rust_validateSequence(103, 3, 4, 100, 10, 0, 1); + assert_eq!(result, 0); + } + #[test] fn determine_block_size_without_delimiters_returns_minimum() { let result = unsafe { ZSTD_rust_determineBlockSize(0, 128, 50, ptr::null(), 0, 0) };