diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index f3cd550e2..a14c929ba 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -105,6 +105,11 @@ ZSTD_inBuffer ZSTD_rust_inBufferForEndFlush(int inBufferMode, const void* expectedSrc, size_t expectedSize, size_t expectedPos); +size_t ZSTD_rust_checkBufferStability( + int inBufferMode, int outBufferMode, + const void* expectedInSrc, size_t expectedInPos, + const void* inputSrc, size_t inputPos, + size_t expectedOutBufferSize, size_t outputSize, size_t outputPos); /* Context-free compression-parameter selection and sizing leaves live in * Rust (rust/src/zstd_compress_params.rs). This file retains @@ -4790,21 +4795,18 @@ ZSTD_setBufferExpectations(ZSTD_CCtx* cctx, const ZSTD_outBuffer* output, const */ static size_t ZSTD_checkBufferStability(ZSTD_CCtx const* cctx, ZSTD_outBuffer const* output, - ZSTD_inBuffer const* input, - ZSTD_EndDirective endOp) + ZSTD_inBuffer const* input) { - if (cctx->appliedParams.inBufferMode == ZSTD_bm_stable) { - ZSTD_inBuffer const expect = cctx->expectedInBuffer; - if (expect.src != input->src || expect.pos != input->pos) - RETURN_ERROR(stabilityCondition_notRespected, "ZSTD_c_stableInBuffer enabled but input differs!"); - } - (void)endOp; - if (cctx->appliedParams.outBufferMode == ZSTD_bm_stable) { - size_t const outBufferSize = output->size - output->pos; - if (cctx->expectedOutBufferSize != outBufferSize) - RETURN_ERROR(stabilityCondition_notRespected, "ZSTD_c_stableOutBuffer enabled but output size differs!"); - } - return 0; + return ZSTD_rust_checkBufferStability( + (int)cctx->appliedParams.inBufferMode, + (int)cctx->appliedParams.outBufferMode, + cctx->expectedInBuffer.src, + cctx->expectedInBuffer.pos, + input->src, + input->pos, + cctx->expectedOutBufferSize, + output->size, + output->pos); } /* @@ -4972,7 +4974,7 @@ size_t ZSTD_compressStream2_c( ZSTD_CCtx* cctx, } /* end of transparent initialization stage */ - FORWARD_IF_ERROR(ZSTD_checkBufferStability(cctx, output, input, endOp), "invalid buffers"); + FORWARD_IF_ERROR(ZSTD_checkBufferStability(cctx, output, input), "invalid buffers"); /* compression stage */ #ifdef ZSTD_MULTITHREAD if (cctx->appliedParams.nbWorkers > 0) { diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index 6ea0884c4..b25c631ed 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -198,6 +198,61 @@ pub extern "C" fn ZSTD_rust_inBufferForEndFlush( } } +#[inline] +fn check_buffer_stability( + in_buffer_mode: c_int, + out_buffer_mode: c_int, + expected_in_src: *const c_void, + expected_in_pos: usize, + input_src: *const c_void, + input_pos: usize, + expected_out_buffer_size: usize, + output_size: usize, + output_pos: usize, +) -> usize { + if in_buffer_mode == ZSTD_BM_STABLE + && (expected_in_src != input_src || expected_in_pos != input_pos) + { + return ERROR(ZstdErrorCode::StabilityConditionNotRespected); + } + + if out_buffer_mode == ZSTD_BM_STABLE + && expected_out_buffer_size != output_size.wrapping_sub(output_pos) + { + return ERROR(ZstdErrorCode::StabilityConditionNotRespected); + } + + 0 +} + +/// Validate the stable input/output buffer expectations without crossing the +/// private `ZSTD_CCtx` layout into Rust. Raw pointers are compared only for +/// identity and are never dereferenced. +#[no_mangle] +pub extern "C" fn ZSTD_rust_checkBufferStability( + in_buffer_mode: c_int, + out_buffer_mode: c_int, + expected_in_src: *const c_void, + expected_in_pos: usize, + input_src: *const c_void, + input_pos: usize, + expected_out_buffer_size: usize, + output_size: usize, + output_pos: usize, +) -> usize { + check_buffer_stability( + in_buffer_mode, + out_buffer_mode, + expected_in_src, + expected_in_pos, + input_src, + input_pos, + expected_out_buffer_size, + output_size, + output_pos, + ) +} + #[inline] fn select_sequence_copier(mode: c_int) -> c_int { debug_assert!( @@ -1404,6 +1459,146 @@ mod tests { } } + #[test] + fn check_buffer_stability_accepts_matching_stable_input() { + let expected_src = b"input".as_ptr().cast::(); + + assert_eq!( + check_buffer_stability( + ZSTD_BM_STABLE, + ZSTD_BM_BUFFERED, + expected_src, + 11, + expected_src, + 11, + 0, + 37, + 5, + ), + 0 + ); + assert_eq!( + ZSTD_rust_checkBufferStability( + ZSTD_BM_STABLE, + ZSTD_BM_BUFFERED, + expected_src, + 11, + expected_src, + 11, + 0, + 37, + 5, + ), + 0 + ); + } + + #[test] + fn check_buffer_stability_rejects_changed_input_pointer_or_position() { + let expected_src = b"input".as_ptr().cast::(); + let other_src = b"other".as_ptr().cast::(); + let error = ERROR(ZstdErrorCode::StabilityConditionNotRespected); + + assert_eq!( + ZSTD_rust_checkBufferStability( + ZSTD_BM_STABLE, + ZSTD_BM_BUFFERED, + expected_src, + 11, + other_src, + 11, + 0, + 37, + 5, + ), + error + ); + assert_eq!( + ZSTD_rust_checkBufferStability( + ZSTD_BM_STABLE, + ZSTD_BM_BUFFERED, + expected_src, + 11, + expected_src, + 12, + 0, + 37, + 5, + ), + error + ); + } + + #[test] + fn check_buffer_stability_ignores_input_changes_in_buffered_mode() { + let expected_src = b"input".as_ptr().cast::(); + let other_src = b"other".as_ptr().cast::(); + + assert_eq!( + ZSTD_rust_checkBufferStability( + ZSTD_BM_BUFFERED, + ZSTD_BM_BUFFERED, + expected_src, + 11, + other_src, + 12, + 0, + 37, + 5, + ), + 0 + ); + } + + #[test] + fn check_buffer_stability_validates_stable_output_remainder() { + let expected_src = b"input".as_ptr().cast::(); + let error = ERROR(ZstdErrorCode::StabilityConditionNotRespected); + + assert_eq!( + ZSTD_rust_checkBufferStability( + ZSTD_BM_BUFFERED, + ZSTD_BM_STABLE, + expected_src, + 0, + ptr::null(), + 0, + 32, + 40, + 8, + ), + 0 + ); + assert_eq!( + ZSTD_rust_checkBufferStability( + ZSTD_BM_BUFFERED, + ZSTD_BM_STABLE, + expected_src, + 0, + ptr::null(), + 0, + 32, + 40, + 7, + ), + error + ); + assert_eq!( + ZSTD_rust_checkBufferStability( + ZSTD_BM_BUFFERED, + ZSTD_BM_STABLE, + expected_src, + 0, + ptr::null(), + 0, + usize::MAX, + 3, + 4, + ), + 0 + ); + } + #[test] fn sequence_copier_selector_returns_no_delimiters_mode() { assert_eq!(