From c90dc280898e6d95d30f4936e895998f8cebeb93 Mon Sep 17 00:00:00 2001 From: Alek P Date: Fri, 29 May 2026 21:13:39 -0400 Subject: [PATCH] enforce exact decompressed length for lz4, gzip, and zstd Decompressors must expand a ZFS block to exactly the expected number of bytes. Treat decompression to an unexpected length as failure, so truncated or short output is not accepted as valid decompression. This makes our handling of decompress return values consistent with the decompression functions' APIs. Reviewed-by: Brian Behlendorf Reviewed-by: Alexander Motin Signed-off-by: Alek Pinchuk Closes #18599 --- module/zfs/gzip.c | 8 ++++++-- module/zfs/lz4_zfs.c | 15 +++++++++++---- module/zstd/zfs_zstd.c | 9 +++++++++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/module/zfs/gzip.c b/module/zfs/gzip.c index d183e998456..2dee3e1da78 100644 --- a/module/zfs/gzip.c +++ b/module/zfs/gzip.c @@ -96,13 +96,17 @@ zfs_gzip_decompress_buf(void *s_start, void *d_start, size_t s_len, /* check if hardware accelerator can be used */ if (qat_dc_use_accel(d_len)) { if (qat_compress(QAT_DECOMPRESS, s_start, s_len, - d_start, d_len, &dstlen) == CPA_STATUS_SUCCESS) - return (0); + d_start, d_len, &dstlen) == CPA_STATUS_SUCCESS) { + if ((size_t)dstlen == d_len) + return (0); + } /* if hardware de-compress fail, do it again with software */ } if (uncompress_func(d_start, &dstlen, s_start, s_len) != Z_OK) return (-1); + if ((size_t)dstlen != d_len) + return (-1); return (0); } diff --git a/module/zfs/lz4_zfs.c b/module/zfs/lz4_zfs.c index 0c03a6855c7..672b1bd27e6 100644 --- a/module/zfs/lz4_zfs.c +++ b/module/zfs/lz4_zfs.c @@ -88,17 +88,24 @@ zfs_lz4_decompress_buf(void *s_start, void *d_start, size_t s_len, (void) n; const char *src = s_start; uint32_t bufsiz = BE_IN32(src); + int decoded; /* invalid compressed buffer size encoded at start */ if (bufsiz + sizeof (bufsiz) > s_len) return (1); /* - * Returns 0 on success (decompression function returned non-negative) - * and non-zero on failure (decompression function returned negative). + * LZ4_uncompress_unknownOutputSize returns the number of bytes decoded + * on success, or a negative value on failure. An OpenZFS block must + * expand to exactly d_len bytes */ - return (LZ4_uncompress_unknownOutputSize(&src[sizeof (bufsiz)], - d_start, bufsiz, d_len) < 0); + decoded = LZ4_uncompress_unknownOutputSize(&src[sizeof (bufsiz)], + d_start, bufsiz, d_len); + if (decoded < 0) + return (1); + if (d_len != (size_t)decoded) + return (1); + return (0); } ZFS_COMPRESS_WRAP_DECL(zfs_lz4_compress) diff --git a/module/zstd/zfs_zstd.c b/module/zstd/zfs_zstd.c index 82212055f0e..f38800f7f34 100644 --- a/module/zstd/zfs_zstd.c +++ b/module/zstd/zfs_zstd.c @@ -682,6 +682,15 @@ zfs_zstd_decompress_level_buf(void *s_start, void *d_start, size_t s_len, return (1); } + /* + * An OpenZFS compressed block must expand to exactly d_len bytes. + * ZSTD_decompressDCtx returns the decompressed size on success. + */ + if (result != d_len) { + ZSTDSTAT_BUMP(zstd_stat_dec_fail); + return (1); + } + if (level) { *level = curlevel; }