From 7fc0b7f2e10c7a1c57e6b0c5ad398a1c8f4a6868 Mon Sep 17 00:00:00 2001 From: huang <13165998033@163.com> Date: Mon, 2 Nov 2026 14:34:27 +0800 Subject: [PATCH] decompress: check content size on empty last block in streaming ZSTD_decompressContinue() verifies decodedSize against the frame's Frame_Content_Size only in the ZSTDds_decompressLastBlock path. An empty last block terminates the frame earlier, in ZSTDds_decodeBlockHeader, so ZSTD_decompressStream() accepted frames that decode fewer bytes than the declared content size, while ZSTD_decompress() rejected them with corruption_detected. Perform the same check when an empty last block ends the frame, and add a regression test to zstreamtest. --- lib/decompress/zstd_decompress.c | 7 +++++++ tests/zstreamtest.c | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/lib/decompress/zstd_decompress.c b/lib/decompress/zstd_decompress.c index 325f0b54dfc..9c3d76513b4 100644 --- a/lib/decompress/zstd_decompress.c +++ b/lib/decompress/zstd_decompress.c @@ -1321,6 +1321,13 @@ size_t ZSTD_decompressContinue(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, c } /* empty block */ if (bp.lastBlock) { + /* An empty last block ends the frame here, so the content size + * check that normally runs in ZSTDds_decompressLastBlock must + * be performed at this point as well. */ + RETURN_ERROR_IF( + dctx->fParams.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN + && dctx->decodedSize != dctx->fParams.frameContentSize, + corruption_detected, ""); if (dctx->fParams.checksumFlag) { dctx->expected = 4; dctx->stage = ZSTDds_checkChecksum; diff --git a/tests/zstreamtest.c b/tests/zstreamtest.c index 88a20a3e270..08577a93208 100644 --- a/tests/zstreamtest.c +++ b/tests/zstreamtest.c @@ -2512,6 +2512,32 @@ static int basicUnitTests(U32 seed, double compressibility, int bigTests) } DISPLAYLEVEL(3, "OK \n"); + DISPLAYLEVEL(3, "test%3i : reject empty last block that underflows frame content size : ", testNb++); + { /* Frame declares 1,000,000 bytes, but only decodes 5 bytes before an + * empty last block ends it. Streaming must report corruption. */ + static const BYTE frame[] = { + 0x28, 0xb5, 0x2f, 0xfd, /* magic number */ + 0xa0, /* single segment, 4-byte FCS */ + 0x40, 0x42, 0x0f, 0x00, /* frame content size: 1,000,000 */ + 0x28, 0x00, 0x00, 'h', 'e', 'l', 'l', 'o', /* non-last raw block, 5 bytes */ + 0x01, 0x00, 0x00, /* empty last raw block */ + }; + ZSTD_DStream* const dstream = ZSTD_createDStream(); + BYTE out[16]; + ZSTD_inBuffer input; + ZSTD_outBuffer output; + size_t r; + CHECK(dstream == NULL, "memory allocation failure"); + input.src = frame; input.size = sizeof(frame); input.pos = 0; + output.dst = out; output.size = sizeof(out); output.pos = 0; + r = ZSTD_decompressStream(dstream, &output, &input); + ZSTD_freeDStream(dstream); + CHECK(!ZSTD_isError(r), "empty last block bypassed the content size check"); + CHECK(ZSTD_getErrorCode(r) != ZSTD_error_corruption_detected, + "unexpected error : %s", ZSTD_getErrorName(r)); + } + DISPLAYLEVEL(3, "OK \n"); + _end: FUZ_freeDictionary(dictionary); ZSTD_freeCStream(zc);