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);