diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 3fd053d0b..11b827e60 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -344,6 +344,27 @@ typedef char ZSTD_rust_create_cctx_state_layout[ == 4 * sizeof(void*) && sizeof(ZSTD_rust_createCCtxState) == 5 * sizeof(void*)) ? 1 : -1]; +typedef void* (*ZSTD_rust_initStaticCCtxInit_f)(void* context); +typedef struct { + void* callbackContext; + void* workspace; + size_t workspaceSize; + size_t cctxSize; + ZSTD_rust_initStaticCCtxInit_f init; +} ZSTD_rust_initStaticCCtxState; +void* ZSTD_rust_initStaticCCtx(const ZSTD_rust_initStaticCCtxState* state); +typedef char ZSTD_rust_init_static_cctx_state_layout[ + (offsetof(ZSTD_rust_initStaticCCtxState, callbackContext) == 0 + && offsetof(ZSTD_rust_initStaticCCtxState, workspace) + == sizeof(void*) + && offsetof(ZSTD_rust_initStaticCCtxState, workspaceSize) + == 2 * sizeof(void*) + && offsetof(ZSTD_rust_initStaticCCtxState, cctxSize) + == 3 * sizeof(void*) + && offsetof(ZSTD_rust_initStaticCCtxState, init) + == 4 * sizeof(void*) + && sizeof(ZSTD_rust_initStaticCCtxState) == 5 * sizeof(void*)) + ? 1 : -1]; typedef void* (*ZSTD_rust_createCDictCreate_f)( void* context, const void* dict, size_t dictSize, int dictLoadMethod, int dictContentType, @@ -2168,20 +2189,26 @@ ZSTD_CCtx* ZSTD_createCCtx_advanced(ZSTD_customMem customMem) return (ZSTD_CCtx*)ZSTD_rust_createCCtx(&state); } -ZSTD_CCtx* ZSTD_initStaticCCtx(void* workspace, size_t workspaceSize) +typedef struct { + void* workspace; + size_t workspaceSize; +} ZSTD_rust_initStaticCCtxContext; + +static void* ZSTD_rust_initStaticCCtx_init(void* opaque) { + ZSTD_rust_initStaticCCtxContext* const context = + (ZSTD_rust_initStaticCCtxContext*)opaque; ZSTD_cwksp ws; ZSTD_CCtx* cctx; - if (workspaceSize <= sizeof(ZSTD_CCtx)) return NULL; /* minimum size */ - if ((size_t)workspace & 7) return NULL; /* must be 8-aligned */ - ZSTD_cwksp_init(&ws, workspace, workspaceSize, ZSTD_cwksp_static_alloc); + ZSTD_cwksp_init(&ws, context->workspace, context->workspaceSize, + ZSTD_cwksp_static_alloc); cctx = (ZSTD_CCtx*)ZSTD_cwksp_reserve_object(&ws, sizeof(ZSTD_CCtx)); if (cctx == NULL) return NULL; ZSTD_memset(cctx, 0, sizeof(ZSTD_CCtx)); ZSTD_cwksp_move(&cctx->workspace, &ws); - cctx->staticSize = workspaceSize; + cctx->staticSize = context->workspaceSize; /* statically sized space. tmpWorkspace never moves (but prev/next block swap places) */ if (!ZSTD_cwksp_check_available(&cctx->workspace, TMP_WORKSPACE_SIZE + 2 * sizeof(ZSTD_compressedBlockState_t))) return NULL; @@ -2193,6 +2220,20 @@ ZSTD_CCtx* ZSTD_initStaticCCtx(void* workspace, size_t workspaceSize) return cctx; } +ZSTD_CCtx* ZSTD_initStaticCCtx(void* workspace, size_t workspaceSize) +{ + ZSTD_rust_initStaticCCtxContext context; + ZSTD_rust_initStaticCCtxState state; + context.workspace = workspace; + context.workspaceSize = workspaceSize; + state.callbackContext = &context; + state.workspace = workspace; + state.workspaceSize = workspaceSize; + state.cctxSize = sizeof(ZSTD_CCtx); + state.init = ZSTD_rust_initStaticCCtx_init; + return (ZSTD_CCtx*)ZSTD_rust_initStaticCCtx(&state); +} + /** * Clears and frees all of the dictionaries in the CCtx. */ diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index d794fc3ca..4fbcf7dbe 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -1324,6 +1324,51 @@ pub unsafe extern "C" fn ZSTD_rust_createCCtx( cctx } +type InitStaticCCtxFn = unsafe extern "C" fn(*mut c_void) -> *mut c_void; +const ZSTD_STATIC_WORKSPACE_ALIGNMENT: usize = 8; + +/// Explicit projection for the public `ZSTD_initStaticCCtx` wrapper. +/// +/// Rust owns workspace pointer/alignment validation, minimum-size validation, +/// and callback ordering. C retains the private static-workspace layout and +/// context initialization behind one callback. +#[repr(C)] +pub struct ZSTD_rust_initStaticCCtxState { + callback_context: *mut c_void, + workspace: *mut c_void, + workspace_size: usize, + cctx_size: usize, + init: InitStaticCCtxFn, +} + +const _: () = { + assert!(offset_of!(ZSTD_rust_initStaticCCtxState, callback_context) == 0); + assert!(offset_of!(ZSTD_rust_initStaticCCtxState, workspace) == size_of::()); + assert!(offset_of!(ZSTD_rust_initStaticCCtxState, workspace_size) == 2 * size_of::()); + assert!(offset_of!(ZSTD_rust_initStaticCCtxState, cctx_size) == 3 * size_of::()); + assert!(offset_of!(ZSTD_rust_initStaticCCtxState, init) == 4 * size_of::()); + assert!(size_of::() == size_of::<[usize; 5]>()); +}; + +/// Validate static CCtx workspace ownership before entering the C leaf. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_initStaticCCtx( + state: *const ZSTD_rust_initStaticCCtxState, +) -> *mut c_void { + if state.is_null() { + return ptr::null_mut(); + } + let state = unsafe { &*state }; + if state.callback_context.is_null() + || state.workspace.is_null() + || (state.workspace as usize) & (ZSTD_STATIC_WORKSPACE_ALIGNMENT - 1) != 0 + || state.workspace_size <= state.cctx_size + { + return ptr::null_mut(); + } + unsafe { (state.init)(state.callback_context) } +} + type FreeCCtxContentFn = unsafe extern "C" fn(*mut c_void); type FreeCCtxObjectFn = unsafe extern "C" fn(*mut c_void); @@ -12045,6 +12090,103 @@ mod tests { assert!(context.initialized.is_null()); } + #[derive(Default)] + struct InitStaticCCtxTestContext { + events: Vec<&'static str>, + result: *mut c_void, + } + + unsafe extern "C" fn init_static_cctx_test_init(context: *mut c_void) -> *mut c_void { + let context = unsafe { &mut *context.cast::() }; + context.events.push("init"); + context.result + } + + fn init_static_cctx_test_state( + context: &mut InitStaticCCtxTestContext, + workspace: *mut c_void, + workspace_size: usize, + cctx_size: usize, + ) -> ZSTD_rust_initStaticCCtxState { + ZSTD_rust_initStaticCCtxState { + callback_context: (context as *mut InitStaticCCtxTestContext).cast(), + workspace, + workspace_size, + cctx_size, + init: init_static_cctx_test_init, + } + } + + #[test] + fn init_static_cctx_rejects_an_unaligned_workspace_before_callback() { + let mut context = InitStaticCCtxTestContext { + result: ptr::dangling_mut(), + ..Default::default() + }; + let mut workspace = [0usize; 2]; + let unaligned_workspace = workspace.as_mut_ptr().cast::().wrapping_add(1).cast(); + let state = init_static_cctx_test_state(&mut context, unaligned_workspace, usize::MAX, 0); + + let result = unsafe { ZSTD_rust_initStaticCCtx(&state) }; + + assert!(result.is_null()); + assert!(context.events.is_empty()); + } + + #[test] + fn init_static_cctx_rejects_a_small_workspace_before_callback() { + let mut context = InitStaticCCtxTestContext { + result: ptr::dangling_mut(), + ..Default::default() + }; + let mut workspace = [0usize; 1]; + let state = init_static_cctx_test_state( + &mut context, + workspace.as_mut_ptr().cast(), + size_of_val(&workspace), + size_of_val(&workspace), + ); + + let result = unsafe { ZSTD_rust_initStaticCCtx(&state) }; + + assert!(result.is_null()); + assert!(context.events.is_empty()); + } + + #[test] + fn init_static_cctx_calls_the_initializer_after_workspace_validation() { + let mut context = InitStaticCCtxTestContext { + result: ptr::dangling_mut(), + ..Default::default() + }; + let mut workspace = [0usize; 1]; + let state = init_static_cctx_test_state( + &mut context, + workspace.as_mut_ptr().cast(), + size_of_val(&workspace), + size_of_val(&workspace) - 1, + ); + + let result = unsafe { ZSTD_rust_initStaticCCtx(&state) }; + + assert_eq!(result, context.result); + assert_eq!(context.events, ["init"]); + } + + #[test] + fn init_static_cctx_rejects_a_null_workspace() { + let mut context = InitStaticCCtxTestContext { + result: ptr::dangling_mut(), + ..Default::default() + }; + let state = init_static_cctx_test_state(&mut context, ptr::null_mut(), usize::MAX, 0); + + let result = unsafe { ZSTD_rust_initStaticCCtx(&state) }; + + assert!(result.is_null()); + assert!(context.events.is_empty()); + } + #[derive(Default)] struct FreeCCtxTestContext { events: Vec<&'static str>,