compress: strictly check if stream finished on decompress

Previously, even when the compressed data was truncated, or had
trailing garbage, the decompressor might not detect it.

To fix the issue, update the check if stream is finished.
With this change, now decompressor_push() must be called with
size == 0 to finalize the stream.
This commit is contained in:
Yu Watanabe
2026-08-27 04:36:57 +09:00
parent d9b6e8663c
commit dac2b39405
2 changed files with 211 additions and 43 deletions
+54 -5
View File
@@ -7,6 +7,7 @@
#include "alloc-util.h"
#include "argv-util.h"
#include "compress.h"
#include "copy.h"
#include "fd-util.h"
#include "io-util.h"
#include "path-util.h"
@@ -392,8 +393,7 @@ TEST(compress_decompress_stream) {
log_debug("/* test faulty decompression */");
ASSERT_OK_ERRNO(lseek(dst, 1, SEEK_SET));
r = decompress_stream(c, dst, dst2, st.st_size);
ASSERT_TRUE(IN_SET(r, 0, -EBADMSG));
ASSERT_ERROR(decompress_stream(c, dst, dst2, st.st_size), EBADMSG);
ASSERT_OK_ERRNO(lseek(dst, 0, SEEK_SET));
ASSERT_OK_ERRNO(lseek(dst2, 0, SEEK_SET));
@@ -520,6 +520,51 @@ TEST(decompress_stream_sparse) {
compare_fd(src, decompressed);
log_debug("/* testing %s sparse decompression with multiple streams */", compression_to_string(c));
{
_cleanup_close_ int multi = -EBADF, compressed_multi = -EBADF, decompressed_multi = -EBADF;
_cleanup_(unlink_tempfilep) char
pattern_multi[] = "/tmp/systemd-test.sparse-multi.XXXXXX",
pattern_compressed_multi[] = "/tmp/systemd-test.sparse-compressed_multi.XXXXXX",
pattern_decompressed_multi[] = "/tmp/systemd-test.sparse-decompressed_multi.XXXXXX";
ASSERT_OK(multi = mkostemp_safe(pattern_multi));
ASSERT_OK(compressed_multi = mkostemp_safe(pattern_compressed_multi));
ASSERT_OK(decompressed_multi = mkostemp_safe(pattern_decompressed_multi));
for (unsigned i = 0; i < 4; i++) {
ASSERT_OK(copy_bytes(src, multi, UINT64_MAX, COPY_SEEK0_SOURCE));
ASSERT_OK(copy_bytes(compressed, compressed_multi, UINT64_MAX, COPY_SEEK0_SOURCE));
}
ASSERT_EQ(lseek(compressed_multi, 0, SEEK_SET), (off_t) 0);
ASSERT_OK(decompress_stream(c, compressed_multi, decompressed_multi, UINT64_MAX));
compare_fd(multi, decompressed_multi);
}
log_debug("/* testing %s sparse decompression with garbage at the end */", compression_to_string(c));
off_t end = ASSERT_OK_ERRNO(lseek(compressed, 0, SEEK_END));
ASSERT_OK(loop_write(compressed, "a", 1));
ASSERT_OK_ERRNO(lseek(compressed, 0, SEEK_SET));
ASSERT_OK_ERRNO(lseek(decompressed, 0, SEEK_SET));
ASSERT_ERROR(decompress_stream(c, compressed, decompressed, st_src.st_size), EBADMSG);
log_debug("/* testing %s sparse decompression with truncated data */", compression_to_string(c));
ASSERT_OK_ERRNO(ftruncate(compressed, end - 1));
ASSERT_OK_ERRNO(lseek(compressed, 0, SEEK_SET));
ASSERT_OK_ERRNO(lseek(decompressed, 0, SEEK_SET));
ASSERT_ERROR(decompress_stream(c, compressed, decompressed, st_src.st_size), EBADMSG);
log_debug("/* testing %s sparse decompression with zero length stream */", compression_to_string(c));
ASSERT_OK_ERRNO(ftruncate(compressed, 0));
ASSERT_OK_ERRNO(lseek(compressed, 0, SEEK_SET));
ASSERT_OK_ERRNO(lseek(decompressed, 0, SEEK_SET));
ASSERT_ERROR(decompress_stream(c, compressed, decompressed, st_src.st_size), EBADMSG);
/* Test all-zeros input: entire output should be a hole */
log_debug("/* testing %s sparse decompression of all-zeros */", compression_to_string(c));
{
@@ -619,9 +664,13 @@ TEST(compressor_decompressor_push_api) {
ASSERT_EQ(compressor_type(decompressor), c);
struct decompressor_test_data result = {};
ASSERT_OK(decompressor_push(decompressor, full_compressed, total_compressed, test_decompressor_callback, &result));
ASSERT_EQ(result.size, sizeof(text));
ASSERT_EQ(memcmp(result.buf, text, sizeof(text)), 0);
for (unsigned i = 0; i < 4; i++)
ASSERT_OK(decompressor_push(decompressor, full_compressed, total_compressed, test_decompressor_callback, &result));
ASSERT_OK(decompressor_push(decompressor, /* data= */ NULL, /* size= */ 0, test_decompressor_callback, &result));
ASSERT_EQ(result.size, sizeof(text) * 4);
for (unsigned i = 0; i < 4; i++)
ASSERT_EQ(memcmp(result.buf + sizeof(text) * i, text, sizeof(text)), 0);
free(result.buf);
decompressor = compressor_free(decompressor);