diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 1ad5856eb..3fd053d0b 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -387,6 +387,27 @@ typedef char ZSTD_rust_free_cdict_state_layout[ == 3 * sizeof(void*) && sizeof(ZSTD_rust_freeCDictState) == 4 * sizeof(void*)) ? 1 : -1]; +typedef void* (*ZSTD_rust_initStaticCDictInit_f)(void* context); +typedef struct { + void* callbackContext; + void* workspace; + size_t workspaceSize; + size_t neededSize; + ZSTD_rust_initStaticCDictInit_f init; +} ZSTD_rust_initStaticCDictState; +void* ZSTD_rust_initStaticCDict(const ZSTD_rust_initStaticCDictState* state); +typedef char ZSTD_rust_init_static_cdict_state_layout[ + (offsetof(ZSTD_rust_initStaticCDictState, callbackContext) == 0 + && offsetof(ZSTD_rust_initStaticCDictState, workspace) + == sizeof(void*) + && offsetof(ZSTD_rust_initStaticCDictState, workspaceSize) + == 2 * sizeof(void*) + && offsetof(ZSTD_rust_initStaticCDictState, neededSize) + == 3 * sizeof(void*) + && offsetof(ZSTD_rust_initStaticCDictState, init) + == 4 * sizeof(void*) + && sizeof(ZSTD_rust_initStaticCDictState) == 5 * sizeof(void*)) + ? 1 : -1]; typedef void (*ZSTD_rust_resetCCtxClearAllDicts_f)(void* context); typedef size_t (*ZSTD_rust_resetCCtxResetParams_f)(void* context); typedef struct { @@ -5602,6 +5623,47 @@ size_t ZSTD_freeCDict(ZSTD_CDict* cdict) * Note : there is no corresponding "free" function. * Since workspace was allocated externally, it must be freed externally. */ +typedef struct { + void* workspace; + size_t workspaceSize; + const void* dict; + size_t dictSize; + ZSTD_dictLoadMethod_e dictLoadMethod; + ZSTD_dictContentType_e dictContentType; + ZSTD_compressionParameters cParams; + ZSTD_ParamSwitch_e useRowMatchFinder; +} ZSTD_rust_initStaticCDictContext; + +static void* ZSTD_rust_initStaticCDict_init(void* opaque) +{ + ZSTD_rust_initStaticCDictContext* const context = + (ZSTD_rust_initStaticCDictContext*)opaque; + ZSTD_cwksp ws; + ZSTD_CDict* cdict; + ZSTD_CCtx_params params; + + ZSTD_cwksp_init(&ws, context->workspace, context->workspaceSize, + ZSTD_cwksp_static_alloc); + cdict = (ZSTD_CDict*)ZSTD_cwksp_reserve_object(&ws, sizeof(ZSTD_CDict)); + if (cdict == NULL) return NULL; + ZSTD_cwksp_move(&cdict->workspace, &ws); + + ZSTD_CCtxParams_init(¶ms, 0); + params.cParams = context->cParams; + params.useRowMatchFinder = context->useRowMatchFinder; + cdict->useRowMatchFinder = context->useRowMatchFinder; + cdict->compressionLevel = ZSTD_NO_CLEVEL; + + if (ZSTD_isError( ZSTD_initCDict_internal(cdict, + context->dict, context->dictSize, + context->dictLoadMethod, + context->dictContentType, + params) )) + return NULL; + + return cdict; +} + const ZSTD_CDict* ZSTD_initStaticCDict( void* workspace, size_t workspaceSize, const void* dict, size_t dictSize, @@ -5617,35 +5679,24 @@ const ZSTD_CDict* ZSTD_initStaticCDict( : ZSTD_cwksp_alloc_size(ZSTD_cwksp_align(dictSize, sizeof(void*)))) + ZSTD_cwksp_alloc_size(HUF_WORKSPACE_SIZE) + matchStateSize; - ZSTD_CDict* cdict; - ZSTD_CCtx_params params; + ZSTD_rust_initStaticCDictContext context; + ZSTD_rust_initStaticCDictState state; DEBUGLOG(4, "ZSTD_initStaticCDict (dictSize==%u)", (unsigned)dictSize); - if ((size_t)workspace & 7) return NULL; /* 8-aligned */ - - { - ZSTD_cwksp ws; - ZSTD_cwksp_init(&ws, workspace, workspaceSize, ZSTD_cwksp_static_alloc); - cdict = (ZSTD_CDict*)ZSTD_cwksp_reserve_object(&ws, sizeof(ZSTD_CDict)); - if (cdict == NULL) return NULL; - ZSTD_cwksp_move(&cdict->workspace, &ws); - } - - if (workspaceSize < neededSize) return NULL; - - ZSTD_CCtxParams_init(¶ms, 0); - params.cParams = cParams; - params.useRowMatchFinder = useRowMatchFinder; - cdict->useRowMatchFinder = useRowMatchFinder; - cdict->compressionLevel = ZSTD_NO_CLEVEL; - - if (ZSTD_isError( ZSTD_initCDict_internal(cdict, - dict, dictSize, - dictLoadMethod, dictContentType, - params) )) - return NULL; - - return cdict; + context.workspace = workspace; + context.workspaceSize = workspaceSize; + context.dict = dict; + context.dictSize = dictSize; + context.dictLoadMethod = dictLoadMethod; + context.dictContentType = dictContentType; + context.cParams = cParams; + context.useRowMatchFinder = useRowMatchFinder; + state.callbackContext = &context; + state.workspace = workspace; + state.workspaceSize = workspaceSize; + state.neededSize = neededSize; + state.init = ZSTD_rust_initStaticCDict_init; + return (const ZSTD_CDict*)ZSTD_rust_initStaticCDict(&state); } ZSTD_compressionParameters ZSTD_getCParamsFromCDict(const ZSTD_CDict* cdict) diff --git a/rust/src/zstd_compress_dictionary.rs b/rust/src/zstd_compress_dictionary.rs index 397bc3674..5e7cd6eca 100644 --- a/rust/src/zstd_compress_dictionary.rs +++ b/rust/src/zstd_compress_dictionary.rs @@ -42,6 +42,7 @@ const ZSTD_MAGIC_DICTIONARY: u32 = 0xEC30_A437; const ZSTD_DCT_AUTO: c_int = 0; const ZSTD_DCT_RAW_CONTENT: c_int = 1; const ZSTD_DCT_FULL_DICT: c_int = 2; +const ZSTD_STATIC_WORKSPACE_ALIGNMENT: usize = 8; /// C keeps match-state/content insertion private because it depends on the /// configuration-sensitive `ZSTD_MatchState_t`, `ldmState_t`, workspace, and @@ -479,6 +480,50 @@ pub unsafe extern "C" fn ZSTD_rust_freeCDict(state: *const ZSTD_rust_freeCDictSt 0 } +type InitStaticCDictFn = unsafe extern "C" fn(*mut c_void) -> *mut c_void; + +/// Explicit projection for the public `ZSTD_initStaticCDict` wrapper. +/// +/// Rust owns workspace pointer/alignment validation, minimum-size validation, +/// and callback ordering. C retains the private static-workspace layout and +/// dictionary initialization behind one callback. +#[repr(C)] +pub struct ZSTD_rust_initStaticCDictState { + callback_context: *mut c_void, + workspace: *mut c_void, + workspace_size: usize, + needed_size: usize, + init: InitStaticCDictFn, +} + +const _: () = { + assert!(offset_of!(ZSTD_rust_initStaticCDictState, callback_context) == 0); + assert!(offset_of!(ZSTD_rust_initStaticCDictState, workspace) == size_of::()); + assert!(offset_of!(ZSTD_rust_initStaticCDictState, workspace_size) == 2 * size_of::()); + assert!(offset_of!(ZSTD_rust_initStaticCDictState, needed_size) == 3 * size_of::()); + assert!(offset_of!(ZSTD_rust_initStaticCDictState, init) == 4 * size_of::()); + assert!(size_of::() == size_of::<[usize; 5]>()); +}; + +/// Validate static CDict workspace ownership before entering the C leaf. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_initStaticCDict( + state: *const ZSTD_rust_initStaticCDictState, +) -> *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.needed_size + { + return ptr::null_mut(); + } + unsafe { (state.init)(state.callback_context) } +} + type CompressBeginUsingDictBeginFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize, *const c_void, u64) -> usize; @@ -2939,6 +2984,103 @@ mod tests { assert_eq!(result, 0); } + #[derive(Default)] + struct InitStaticCDictProbe { + events: Vec<&'static str>, + result: *mut c_void, + } + + unsafe extern "C" fn init_static_cdict_test_init(context: *mut c_void) -> *mut c_void { + let probe = unsafe { &mut *context.cast::() }; + probe.events.push("init"); + probe.result + } + + fn init_static_cdict_test_state( + probe: &mut InitStaticCDictProbe, + workspace: *mut c_void, + workspace_size: usize, + needed_size: usize, + ) -> ZSTD_rust_initStaticCDictState { + ZSTD_rust_initStaticCDictState { + callback_context: (probe as *mut InitStaticCDictProbe).cast(), + workspace, + workspace_size, + needed_size, + init: init_static_cdict_test_init, + } + } + + #[test] + fn init_static_cdict_rejects_an_unaligned_workspace_before_callback() { + let mut probe = InitStaticCDictProbe { + 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_cdict_test_state(&mut probe, unaligned_workspace, usize::MAX, 0); + + let result = unsafe { ZSTD_rust_initStaticCDict(&state) }; + + assert!(result.is_null()); + assert!(probe.events.is_empty()); + } + + #[test] + fn init_static_cdict_rejects_insufficient_workspace_before_callback() { + let mut probe = InitStaticCDictProbe { + result: ptr::dangling_mut(), + ..Default::default() + }; + let mut workspace = [0usize; 1]; + let state = init_static_cdict_test_state( + &mut probe, + workspace.as_mut_ptr().cast(), + size_of_val(&workspace), + size_of_val(&workspace) + 1, + ); + + let result = unsafe { ZSTD_rust_initStaticCDict(&state) }; + + assert!(result.is_null()); + assert!(probe.events.is_empty()); + } + + #[test] + fn init_static_cdict_calls_the_initializer_after_workspace_validation() { + let mut probe = InitStaticCDictProbe { + result: ptr::dangling_mut(), + ..Default::default() + }; + let mut workspace = [0usize; 1]; + let state = init_static_cdict_test_state( + &mut probe, + workspace.as_mut_ptr().cast(), + size_of_val(&workspace), + size_of_val(&workspace), + ); + + let result = unsafe { ZSTD_rust_initStaticCDict(&state) }; + + assert_eq!(result, probe.result); + assert_eq!(probe.events, ["init"]); + } + + #[test] + fn init_static_cdict_rejects_a_null_workspace() { + let mut probe = InitStaticCDictProbe { + result: ptr::dangling_mut(), + ..Default::default() + }; + let state = init_static_cdict_test_state(&mut probe, ptr::null_mut(), usize::MAX, 0); + + let result = unsafe { ZSTD_rust_initStaticCDict(&state) }; + + assert!(result.is_null()); + assert!(probe.events.is_empty()); + } + #[derive(Default)] struct CompressBeginUsingDictProbe { events: Vec<&'static str>,