diff --git a/lib/decompress/zstd_decompress.c b/lib/decompress/zstd_decompress.c index 325f0b54dfc..7299b8cafa5 100644 --- a/lib/decompress/zstd_decompress.c +++ b/lib/decompress/zstd_decompress.c @@ -345,7 +345,13 @@ size_t ZSTD_freeDCtx(ZSTD_DCtx* dctx) void ZSTD_copyDCtx(ZSTD_DCtx* dstDCtx, const ZSTD_DCtx* srcDCtx) { size_t const toCopy = (size_t)((char*)(&dstDCtx->inBuff) - (char*)dstDCtx); + ZSTD_customMem const dstCustomMem = dstDCtx->customMem; ZSTD_memcpy(dstDCtx, srcDCtx, toCopy); /* no need to copy workspace */ + /* keep the destination's allocator: the destination context was + * allocated (and will be freed) with its own customMem. + * Copying the source's allocator instead would make ZSTD_freeDCtx() + * release a default-allocated context with mismatched callbacks. */ + dstDCtx->customMem = dstCustomMem; } /* Given a dctx with a digested frame params, re-selects the correct ZSTD_DDict based on diff --git a/tests/fuzzer.c b/tests/fuzzer.c index 32360b9a58d..2dd61fa7671 100644 --- a/tests/fuzzer.c +++ b/tests/fuzzer.c @@ -153,6 +153,22 @@ static U32 FUZ_highbit32(U32 v32) /*============================================= * Memory Tests =============================================*/ +/* allocator used by the ZSTD_copyDCtx() regression test: + * customFree flags when it is called on memory it must not own */ +static void* FUZ_countingAlloc(void* counter, size_t size) +{ + int* const flag = (int*)counter; + (void)flag; + return malloc(size); +} + +static void FUZ_countingWrongFree(void* counter, void* address) +{ + int* const flag = (int*)counter; + *flag += 1; /* this free() must never happen for the destination ctx */ + free(address); +} + #if defined(__APPLE__) && defined(__MACH__) #include /* malloc_size */ @@ -1148,6 +1164,26 @@ static int basicUnitTests(U32 const seed, double compressibility) } DISPLAYLEVEL(3, "OK \n"); + DISPLAYLEVEL(3, "test%3i : ZSTD_copyDCtx keeps the destination's allocator : ", testNb++); + { /* ZSTD_copyDCtx() used to memcpy the source's customMem over the + * destination's one. When both contexts were created with different + * allocators, ZSTD_freeDCtx() would then release the destination + * context (and its resources) with the wrong callbacks. + * Here the mismatch is detected without faulting: the destination + * uses one that must never be called for the copy. */ + static int wrongFreeCalled; + wrongFreeCalled = 0; + ZSTD_customMem const wrongMem = { FUZ_countingAlloc, FUZ_countingWrongFree, &wrongFreeCalled }; + ZSTD_DCtx* const src = ZSTD_createDCtx_advanced(wrongMem); + ZSTD_DCtx* const dst = ZSTD_createDCtx(); /* default allocator */ + assert(src != NULL); assert(dst != NULL); + ZSTD_copyDCtx(dst, src); + ZSTD_freeDCtx(dst); /* must use dst's own allocator */ + if (wrongFreeCalled) goto _output_error; /* free(dst) went through src's callbacks */ + ZSTD_freeDCtx(src); /* src's own free through src's callbacks is fine */ + } + DISPLAYLEVEL(3, "OK \n"); + DISPLAYLEVEL(3, "test%3i : misc unaccounted for zstd symbols : ", testNb++); { /* %p takes a void*. In ISO C, it's illegal to cast a function pointer