From 9ecbf1ad4ea878c592fdee73e43850a6f625e0d1 Mon Sep 17 00:00:00 2001 From: Filippo Tedeschi Date: Thu, 1 Oct 2026 12:35:34 +0200 Subject: [PATCH] compress, decompress: validate NULL pointers in CCtx, DCtx, and streaming entry points Add defensive parameter validation against NULL pointers across public CCtx, DCtx, and streaming APIs: 1. In lib/compress/zstd_compress.c: - Validate cctx != NULL in ZSTD_CCtx_reset, ZSTD_CCtx_setParameter, ZSTD_CCtx_getParameter, and ZSTD_CCtx_setPledgedSrcSize before accessing streamStage or struct members. - Validate CCtxParams != NULL and value != NULL in ZSTD_CCtxParams_setParameter and ZSTD_CCtxParams_getParameter. - Validate cctx, output, and input pointers in ZSTD_compressStream2 before checking pos and size bounds. - Validate dstPos != NULL and srcPos != NULL in ZSTD_compressStream2_simpleArgs. - Validate cctx != NULL in ZSTD_compress2 and return 0 for NULL cctx in ZSTD_getBlockSize_deprecated. - Validate zcs != NULL and output != NULL in ZSTD_flushStream and ZSTD_endStream. 2. In lib/decompress/zstd_decompress.c: - Validate dctx != NULL in ZSTD_DCtx_reset, ZSTD_DCtx_setParameter, ZSTD_DCtx_getParameter, ZSTD_decompressBegin, and ZSTD_decompressBegin_usingDDict. - Validate zds, output, and input pointers in ZSTD_decompressStream prior to reading buffer fields. - Validate dstPos != NULL and srcPos != NULL in ZSTD_decompressStream_simpleArgs. - Return 0 on NULL dctx in ZSTD_nextSrcSizeToDecompress and ZSTD_nextSrcSizeToDecompressWithInputSize; return ZSTDnit_frameHeader on NULL dctx in ZSTD_nextInputType. 3. In tests/zstreamtest.c: - Add unit test coverage in basicUnitTests verifying that NULL pointer arguments are rejected with appropriate error codes rather than crashing. Signed-off-by: Filippo Tedeschi --- lib/compress/zstd_compress.c | 30 ++++++++++++++++++++------ lib/decompress/zstd_decompress.c | 37 ++++++++++++++++++++++++-------- tests/zstreamtest.c | 22 +++++++++++++++++++ 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 38c3fc0d4cd..a9754f6c61f 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -729,6 +729,7 @@ static int ZSTD_isUpdateAuthorized(ZSTD_cParameter param) size_t ZSTD_CCtx_setParameter(ZSTD_CCtx* cctx, ZSTD_cParameter param, int value) { DEBUGLOG(4, "ZSTD_CCtx_setParameter (%i, %i)", (int)param, value); + RETURN_ERROR_IF(!cctx, GENERIC, "NULL pointer!"); if (cctx->streamStage != zcss_init) { if (ZSTD_isUpdateAuthorized(param)) { cctx->cParamsChanged = 1; @@ -792,6 +793,7 @@ size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* CCtxParams, ZSTD_cParameter param, int value) { DEBUGLOG(4, "ZSTD_CCtxParams_setParameter (%i, %i)", (int)param, value); + RETURN_ERROR_IF(!CCtxParams, GENERIC, "NULL pointer!"); switch(param) { case ZSTD_c_format : @@ -1043,12 +1045,14 @@ size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* CCtxParams, size_t ZSTD_CCtx_getParameter(ZSTD_CCtx const* cctx, ZSTD_cParameter param, int* value) { + RETURN_ERROR_IF(!cctx || !value, GENERIC, "NULL pointer!"); return ZSTD_CCtxParams_getParameter(&cctx->requestedParams, param, value); } size_t ZSTD_CCtxParams_getParameter( ZSTD_CCtx_params const* CCtxParams, ZSTD_cParameter param, int* value) { + RETURN_ERROR_IF(!CCtxParams || !value, GENERIC, "NULL pointer!"); switch(param) { case ZSTD_c_format : @@ -1251,6 +1255,7 @@ size_t ZSTD_CCtx_setParams(ZSTD_CCtx* cctx, ZSTD_parameters params) size_t ZSTD_CCtx_setPledgedSrcSize(ZSTD_CCtx* cctx, unsigned long long pledgedSrcSize) { DEBUGLOG(4, "ZSTD_CCtx_setPledgedSrcSize to %llu bytes", pledgedSrcSize); + RETURN_ERROR_IF(!cctx, GENERIC, "NULL pointer!"); RETURN_ERROR_IF(cctx->streamStage != zcss_init, stage_wrong, "Can't set pledgedSrcSize when not in init stage."); cctx->pledgedSrcSizePlusOne = pledgedSrcSize+1; @@ -1387,6 +1392,7 @@ size_t ZSTD_CCtx_refPrefix_advanced( * Also dumps dictionary */ size_t ZSTD_CCtx_reset(ZSTD_CCtx* cctx, ZSTD_ResetDirective reset) { + RETURN_ERROR_IF(!cctx, GENERIC, "NULL pointer!"); if ( (reset == ZSTD_reset_session_only) || (reset == ZSTD_reset_session_and_parameters) ) { cctx->streamStage = zcss_init; @@ -4897,7 +4903,9 @@ size_t ZSTD_compressContinue(ZSTD_CCtx* cctx, static size_t ZSTD_getBlockSize_deprecated(const ZSTD_CCtx* cctx) { - ZSTD_compressionParameters const cParams = cctx->appliedParams.cParams; + ZSTD_compressionParameters cParams; + if (cctx == NULL) return 0; + cParams = cctx->appliedParams.cParams; assert(!ZSTD_checkCParams(cParams)); return MIN(cctx->appliedParams.maxBlockSize, (size_t)1 << cParams.windowLog); } @@ -6480,6 +6488,7 @@ size_t ZSTD_compressStream2( ZSTD_CCtx* cctx, { DEBUGLOG(5, "ZSTD_compressStream2, endOp=%u ", (unsigned)endOp); /* check conditions */ + RETURN_ERROR_IF(!cctx || !output || !input, GENERIC, "NULL pointer!"); RETURN_ERROR_IF(output->pos > output->size, dstSize_tooSmall, "invalid output buffer"); RETURN_ERROR_IF(input->pos > input->size, srcSize_wrong, "invalid input buffer"); RETURN_ERROR_IF((U32)endOp > (U32)ZSTD_e_end, parameter_outOfBound, "invalid endDirective"); @@ -6580,6 +6589,7 @@ size_t ZSTD_compressStream2_simpleArgs ( { ZSTD_outBuffer output; ZSTD_inBuffer input; + RETURN_ERROR_IF(!dstPos || !srcPos, GENERIC, "NULL pointer!"); output.dst = dst; output.size = dstCapacity; output.pos = *dstPos; @@ -6598,9 +6608,12 @@ size_t ZSTD_compress2(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize) { - ZSTD_bufferMode_e const originalInBufferMode = cctx->requestedParams.inBufferMode; - ZSTD_bufferMode_e const originalOutBufferMode = cctx->requestedParams.outBufferMode; + ZSTD_bufferMode_e originalInBufferMode; + ZSTD_bufferMode_e originalOutBufferMode; DEBUGLOG(4, "ZSTD_compress2 (srcSize=%u)", (unsigned)srcSize); + RETURN_ERROR_IF(!cctx, GENERIC, "NULL pointer!"); + originalInBufferMode = cctx->requestedParams.inBufferMode; + originalOutBufferMode = cctx->requestedParams.outBufferMode; ZSTD_CCtx_reset(cctx, ZSTD_reset_session_only); /* Enable stable input/output buffers. */ cctx->requestedParams.inBufferMode = ZSTD_bm_stable; @@ -8178,15 +8191,20 @@ static ZSTD_inBuffer inBuffer_forEndFlush(const ZSTD_CStream* zcs) * @return : amount of data remaining to flush */ size_t ZSTD_flushStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output) { - ZSTD_inBuffer input = inBuffer_forEndFlush(zcs); + ZSTD_inBuffer input; + RETURN_ERROR_IF(!zcs || !output, GENERIC, "NULL pointer!"); + input = inBuffer_forEndFlush(zcs); input.size = input.pos; /* do not ingest more input during flush */ return ZSTD_compressStream2(zcs, output, &input, ZSTD_e_flush); } size_t ZSTD_endStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output) { - ZSTD_inBuffer input = inBuffer_forEndFlush(zcs); - size_t const remainingToFlush = ZSTD_compressStream2(zcs, output, &input, ZSTD_e_end); + ZSTD_inBuffer input; + size_t remainingToFlush; + RETURN_ERROR_IF(!zcs || !output, GENERIC, "NULL pointer!"); + input = inBuffer_forEndFlush(zcs); + remainingToFlush = ZSTD_compressStream2(zcs, output, &input, ZSTD_e_end); FORWARD_IF_ERROR(remainingToFlush , "ZSTD_compressStream2(,,ZSTD_e_end) failed"); if (zcs->appliedParams.nbWorkers > 0) return remainingToFlush; /* minimal estimation */ /* single thread mode : attempt to calculate remaining to flush more precisely */ diff --git a/lib/decompress/zstd_decompress.c b/lib/decompress/zstd_decompress.c index 325f0b54dfc..df4ebcdc185 100644 --- a/lib/decompress/zstd_decompress.c +++ b/lib/decompress/zstd_decompress.c @@ -1220,7 +1220,7 @@ size_t ZSTD_decompress(void* dst, size_t dstCapacity, const void* src, size_t sr * Advanced Streaming Decompression API * Bufferless and synchronous ****************************************/ -size_t ZSTD_nextSrcSizeToDecompress(ZSTD_DCtx* dctx) { return dctx->expected; } +size_t ZSTD_nextSrcSizeToDecompress(ZSTD_DCtx* dctx) { return dctx ? dctx->expected : 0; } /** * Similar to ZSTD_nextSrcSizeToDecompress(), but when a block input can be streamed, we @@ -1233,6 +1233,7 @@ size_t ZSTD_nextSrcSizeToDecompress(ZSTD_DCtx* dctx) { return dctx->expected; } * @param inputSize - The total amount of input that the caller currently has. */ static size_t ZSTD_nextSrcSizeToDecompressWithInputSize(ZSTD_DCtx* dctx, size_t inputSize) { + if (!dctx) return 0; if (!(dctx->stage == ZSTDds_decompressBlock || dctx->stage == ZSTDds_decompressLastBlock)) return dctx->expected; if (dctx->bType != bt_raw) @@ -1241,6 +1242,7 @@ static size_t ZSTD_nextSrcSizeToDecompressWithInputSize(ZSTD_DCtx* dctx, size_t } ZSTD_nextInputType_e ZSTD_nextInputType(ZSTD_DCtx* dctx) { + if (dctx == NULL) return ZSTDnit_frameHeader; switch(dctx->stage) { default: /* should not happen */ @@ -1558,6 +1560,7 @@ static size_t ZSTD_decompress_insertDictionary(ZSTD_DCtx* dctx, const void* dict size_t ZSTD_decompressBegin(ZSTD_DCtx* dctx) { + RETURN_ERROR_IF(!dctx, GENERIC, "NULL pointer!"); assert(dctx != NULL); #if ZSTD_TRACE dctx->traceCtx = (ZSTD_trace_decompress_begin != NULL) ? ZSTD_trace_decompress_begin(dctx) : 0; @@ -1600,6 +1603,7 @@ size_t ZSTD_decompressBegin_usingDict(ZSTD_DCtx* dctx, const void* dict, size_t size_t ZSTD_decompressBegin_usingDDict(ZSTD_DCtx* dctx, const ZSTD_DDict* ddict) { DEBUGLOG(4, "ZSTD_decompressBegin_usingDDict"); + RETURN_ERROR_IF(!dctx, GENERIC, "NULL pointer!"); assert(dctx != NULL); if (ddict) { const char* const dictStart = (const char*)ZSTD_DDict_dictContent(ddict); @@ -1875,6 +1879,7 @@ static int ZSTD_dParam_withinBounds(ZSTD_dParameter dParam, int value) size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param, int* value) { + RETURN_ERROR_IF(!dctx || !value, GENERIC, "NULL pointer!"); switch (param) { case ZSTD_d_windowLogMax: *value = (int)ZSTD_highbit32((U32)dctx->maxWindowSize); @@ -1904,6 +1909,7 @@ size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param, int* value size_t ZSTD_DCtx_setParameter(ZSTD_DCtx* dctx, ZSTD_dParameter dParam, int value) { + RETURN_ERROR_IF(!dctx, GENERIC, "NULL pointer!"); RETURN_ERROR_IF(dctx->streamStage != zdss_init, stage_wrong, ""); switch(dParam) { case ZSTD_d_windowLogMax: @@ -1945,6 +1951,7 @@ size_t ZSTD_DCtx_setParameter(ZSTD_DCtx* dctx, ZSTD_dParameter dParam, int value size_t ZSTD_DCtx_reset(ZSTD_DCtx* dctx, ZSTD_ResetDirective reset) { + RETURN_ERROR_IF(!dctx, GENERIC, "NULL pointer!"); if ( (reset == ZSTD_reset_session_only) || (reset == ZSTD_reset_session_and_parameters) ) { dctx->streamStage = zdss_init; @@ -2084,17 +2091,18 @@ static size_t ZSTD_decompressContinueStream( size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inBuffer* input) { - const char* const src = (const char*)input->src; - const char* const istart = input->pos != 0 ? src + input->pos : src; - const char* const iend = input->size != 0 ? src + input->size : src; - const char* ip = istart; - char* const dst = (char*)output->dst; - char* const ostart = output->pos != 0 ? dst + output->pos : dst; - char* const oend = output->size != 0 ? dst + output->size : dst; - char* op = ostart; + const char* src; + const char* istart; + const char* iend; + const char* ip; + char* dst; + char* ostart; + char* oend; + char* op; U32 someMoreWork = 1; DEBUGLOG(5, "ZSTD_decompressStream"); + RETURN_ERROR_IF(!zds || !output || !input, GENERIC, "NULL pointer!"); assert(zds != NULL); RETURN_ERROR_IF( input->pos > input->size, @@ -2106,6 +2114,16 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB dstSize_tooSmall, "forbidden. out: pos: %u vs size: %u", (U32)output->pos, (U32)output->size); + + src = (const char*)input->src; + istart = input->pos != 0 ? src + input->pos : src; + iend = input->size != 0 ? src + input->size : src; + ip = istart; + dst = (char*)output->dst; + ostart = output->pos != 0 ? dst + output->pos : dst; + oend = output->size != 0 ? dst + output->size : dst; + op = ostart; + DEBUGLOG(5, "input size : %u", (U32)(input->size - input->pos)); FORWARD_IF_ERROR(ZSTD_checkOutBuffer(zds, output), ""); @@ -2395,6 +2413,7 @@ size_t ZSTD_decompressStream_simpleArgs ( { ZSTD_outBuffer output; ZSTD_inBuffer input; + RETURN_ERROR_IF(!dstPos || !srcPos, GENERIC, "NULL pointer!"); output.dst = dst; output.size = dstCapacity; output.pos = *dstPos; diff --git a/tests/zstreamtest.c b/tests/zstreamtest.c index 88a20a3e270..42dc9255b78 100644 --- a/tests/zstreamtest.c +++ b/tests/zstreamtest.c @@ -301,6 +301,28 @@ static int basicUnitTests(U32 seed, double compressibility, int bigTests) } dictID = ZDICT_getDictID(dictionary.start, dictionary.filled); + /* Defensive NULL pointer checks */ + DISPLAYLEVEL(3, "test%3i : defensive NULL pointer handling : ", testNb++); + { + int val = 0; + CHECK(!ZSTD_isError(ZSTD_CCtx_setParameter(NULL, ZSTD_c_compressionLevel, 1)), "Expected error on NULL CCtx setParameter"); + CHECK(!ZSTD_isError(ZSTD_CCtx_getParameter(NULL, ZSTD_c_compressionLevel, &val)), "Expected error on NULL CCtx getParameter"); + CHECK(!ZSTD_isError(ZSTD_CCtx_getParameter(zc, ZSTD_c_compressionLevel, NULL)), "Expected error on NULL value in CCtx getParameter"); + CHECK(!ZSTD_isError(ZSTD_CCtx_reset(NULL, ZSTD_reset_session_only)), "Expected error on NULL CCtx reset"); + CHECK(!ZSTD_isError(ZSTD_CCtx_setPledgedSrcSize(NULL, 100)), "Expected error on NULL CCtx setPledgedSrcSize"); + CHECK(!ZSTD_isError(ZSTD_CCtxParams_setParameter(NULL, ZSTD_c_compressionLevel, 1)), "Expected error on NULL CCtxParams setParameter"); + CHECK(!ZSTD_isError(ZSTD_CCtxParams_getParameter(NULL, ZSTD_c_compressionLevel, &val)), "Expected error on NULL CCtxParams getParameter"); + CHECK(!ZSTD_isError(ZSTD_DCtx_setParameter(NULL, ZSTD_d_windowLogMax, 20)), "Expected error on NULL DCtx setParameter"); + CHECK(!ZSTD_isError(ZSTD_DCtx_getParameter(NULL, ZSTD_d_windowLogMax, &val)), "Expected error on NULL DCtx getParameter"); + CHECK(!ZSTD_isError(ZSTD_DCtx_getParameter(zd, ZSTD_d_windowLogMax, NULL)), "Expected error on NULL value in DCtx getParameter"); + CHECK(!ZSTD_isError(ZSTD_DCtx_reset(NULL, ZSTD_reset_session_only)), "Expected error on NULL DCtx reset"); + CHECK(!ZSTD_isError(ZSTD_decompressBegin(NULL)), "Expected error on NULL DCtx decompressBegin"); + CHECK(!ZSTD_isError(ZSTD_decompressBegin_usingDDict(NULL, NULL)), "Expected error on NULL DCtx decompressBegin_usingDDict"); + CHECK(ZSTD_nextSrcSizeToDecompress(NULL) != 0, "Expected 0 on NULL DCtx nextSrcSizeToDecompress"); + CHECK(ZSTD_getBlockSize(NULL) != 0, "Expected 0 on NULL CCtx getBlockSize"); + } + DISPLAYLEVEL(3, "OK \n"); + /* Basic compression test */ DISPLAYLEVEL(3, "test%3i : compress %u bytes : ", testNb++, COMPRESSIBLE_NOISE_LENGTH); CHECK_Z( ZSTD_initCStream(zc, 1 /* cLevel */) );