From: Zbigniew Jędrzejewski-Szmek Date: Sat, 18 Jul 2020 19:39:03 +0000 (+0200) Subject: journal/compress: fix zstd decompression with capped output size X-Git-Tag: v246-rc2~23^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a24153279e078200003da0a8cd7a89b149c1e993;p=thirdparty%2Fsystemd.git journal/compress: fix zstd decompression with capped output size decompress_blob_zstd() would allocate ever bigger buffers in a loop trying to get a buffer big enough to decompress the input data. This is wasteful, since we can just query the size of the decompressed data from the compressed header. Worse, it doesn't work when the output size is capped, i.e. when dst_max != 0. If the decompressed blob happened to be bigger than dst_max, decompression would fail with -ENOBUFS. We need to use "stream decompression" instead, and only get min(uncompressed size, dst_max) bytes of output. Fixes https://bugzilla.redhat.com/show_bug.cgi?id=1856037 in a second way. --- diff --git a/src/journal/compress.c b/src/journal/compress.c index 1b0c01a8fb3..626e079fefb 100644 --- a/src/journal/compress.c +++ b/src/journal/compress.c @@ -259,10 +259,10 @@ int decompress_blob_lz4(const void *src, uint64_t src_size, int decompress_blob_zstd( const void *src, uint64_t src_size, - void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max) { + void **dst, size_t *dst_alloc_size, size_t *dst_size, size_t dst_max) { #if HAVE_ZSTD - size_t space; + uint64_t size; assert(src); assert(src_size > 0); @@ -271,38 +271,40 @@ int decompress_blob_zstd( assert(dst_size); assert(*dst_alloc_size == 0 || *dst); - if (src_size > SIZE_MAX/2) /* Overflow? */ - return -ENOBUFS; - space = src_size * 2; - if (dst_max > 0 && space > dst_max) - space = dst_max; - - if (!greedy_realloc(dst, dst_alloc_size, space, 1)) - return -ENOMEM; + size = ZSTD_getFrameContentSize(src, src_size); + if (IN_SET(size, ZSTD_CONTENTSIZE_ERROR, ZSTD_CONTENTSIZE_UNKNOWN)) + return -EBADMSG; - for (;;) { - size_t k; + if (dst_max > 0 && size > dst_max) + size = dst_max; + if (size > SIZE_MAX) + return -E2BIG; - k = ZSTD_decompress(*dst, *dst_alloc_size, src, src_size); - if (!ZSTD_isError(k)) { - *dst_size = k; - return 0; - } - if (ZSTD_getErrorCode(k) != ZSTD_error_dstSize_tooSmall) - return zstd_ret_to_errno(k); + if (!(greedy_realloc(dst, dst_alloc_size, MAX(ZSTD_DStreamOutSize(), size), 1))) + return -ENOMEM; - if (dst_max > 0 && space >= dst_max) /* Already at max? */ - return -ENOBUFS; - if (space > SIZE_MAX / 2) /* Overflow? */ - return -ENOBUFS; + _cleanup_(ZSTD_freeDCtxp) ZSTD_DCtx *dctx = ZSTD_createDCtx(); + if (!dctx) + return -ENOMEM; - space *= 2; - if (dst_max > 0 && space > dst_max) - space = dst_max; + ZSTD_inBuffer input = { + .src = src, + .size = src_size, + }; + ZSTD_outBuffer output = { + .dst = *dst, + .size = *dst_alloc_size, + }; - if (!greedy_realloc(dst, dst_alloc_size, space, 1)) - return -ENOMEM; + size_t k = ZSTD_decompressStream(dctx, &output, &input); + if (ZSTD_isError(k)) { + log_debug("ZSTD decoder failed: %s", ZSTD_getErrorName(k)); + return zstd_ret_to_errno(k); } + assert(output.pos >= size); + + *dst_size = size; + return 0; #else return -EPROTONOSUPPORT; #endif