]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix bug in archive streamer with LZ4 decompression
authorMichael Paquier <michael@paquier.xyz>
Wed, 2 Jul 2025 04:48:36 +0000 (13:48 +0900)
committerMichael Paquier <michael@paquier.xyz>
Wed, 2 Jul 2025 04:48:36 +0000 (13:48 +0900)
When decompressing some input data, the calculation for the initial
starting point and the initial size were incorrect, potentially leading
to failures when decompressing contents with LZ4.  These initialization
points are fixed in this commit, bringing the logic closer to what
exists for gzip and zstd.

The contents of the compressed data is clear (for example backups taken
with LZ4 can still be decompressed with a "lz4" command), only the
decompression part reading the input data was impacted by this issue.

This code path impacts pg_basebackup and pg_verifybackup, which can use
the LZ4 decompression routines with an archive streamer, or any tools
that try to use the archive streamers in src/fe_utils/.

The issue is easier to reproduce with files that have a low-compression
rate, like ones filled with random data, for a size of at least 512kB,
but this could happen with anything as long as it is stored in a data
folder.  Some tests are added based on this idea, with a file filled
with random bytes grabbed from the backend, written at the root of the
data folder.  This is proving good enough to reproduce the original
problem.

Author: Mikhail Gribkov <youzhick@gmail.com>
Discussion: https://postgr.es/m/CAMEv5_uQS1Hg6KCaEP2JkrTBbZ-nXQhxomWrhYQvbdzR-zy-wA@mail.gmail.com
Backpatch-through: 15

src/bin/pg_verifybackup/t/008_untar.pl
src/bin/pg_verifybackup/t/010_client_untar.pl
src/fe_utils/astreamer_lz4.c

index deed3ec247d2de8340c1949d17189363e38b34ff..bc3d6b352ad503c1cc9f3d61def5f27f7d5be7fe 100644 (file)
@@ -16,6 +16,22 @@ my $primary = PostgreSQL::Test::Cluster->new('primary');
 $primary->init(allows_streaming => 1);
 $primary->start;
 
+# Create file with some random data and an arbitrary size, useful to check
+# the solidity of the compression and decompression logic.  The size of the
+# file is chosen to be around 640kB.  This has proven to be large enough to
+# detect some issues related to LZ4, and low enough to not impact the runtime
+# of the test significantly.
+my $junk_data = $primary->safe_psql(
+       'postgres', qq(
+               SELECT string_agg(encode(sha256(i::bytea), 'hex'), '')
+               FROM generate_series(1, 10240) s(i);));
+my $data_dir = $primary->data_dir;
+my $junk_file = "$data_dir/junk";
+open my $jf, '>', $junk_file
+  or die "Could not create junk file: $!";
+print $jf $junk_data;
+close $jf;
+
 # Create a tablespace directory.
 my $source_ts_path = PostgreSQL::Test::Utils::tempdir_short();
 
@@ -52,6 +68,12 @@ my @test_configuration = (
                'backup_archive' => [ 'base.tar.lz4', "$tsoid.tar.lz4" ],
                'enabled' => check_pg_config("#define USE_LZ4 1")
        },
+       {
+               'compression_method' => 'lz4',
+               'backup_flags' => [ '--compress', 'server-lz4:5' ],
+               'backup_archive' => [ 'base.tar.lz4', "$tsoid.tar.lz4" ],
+               'enabled' => check_pg_config("#define USE_LZ4 1")
+       },
        {
                'compression_method' => 'zstd',
                'backup_flags' => [ '--compress', 'server-zstd' ],
index d8d2b06c7ee86f6aa0bafb8937717b94cb7b32cd..b62faeb5acfabb7b7750b8bb7bb5d765f0785c48 100644 (file)
@@ -15,6 +15,22 @@ my $primary = PostgreSQL::Test::Cluster->new('primary');
 $primary->init(allows_streaming => 1);
 $primary->start;
 
+# Create file with some random data and an arbitrary size, useful to check
+# the solidity of the compression and decompression logic.  The size of the
+# file is chosen to be around 640kB.  This has proven to be large enough to
+# detect some issues related to LZ4, and low enough to not impact the runtime
+# of the test significantly.
+my $junk_data = $primary->safe_psql(
+       'postgres', qq(
+               SELECT string_agg(encode(sha256(i::bytea), 'hex'), '')
+               FROM generate_series(1, 10240) s(i);));
+my $data_dir = $primary->data_dir;
+my $junk_file = "$data_dir/junk";
+open my $jf, '>', $junk_file
+  or die "Could not create junk file: $!";
+print $jf $junk_data;
+close $jf;
+
 my $backup_path = $primary->backup_dir . '/client-backup';
 my $extract_path = $primary->backup_dir . '/extracted-backup';
 
@@ -37,6 +53,12 @@ my @test_configuration = (
                'backup_archive' => 'base.tar.lz4',
                'enabled' => check_pg_config("#define USE_LZ4 1")
        },
+       {
+               'compression_method' => 'lz4',
+               'backup_flags' => [ '--compress', 'client-lz4:1' ],
+               'backup_archive' => 'base.tar.lz4',
+               'enabled' => check_pg_config("#define USE_LZ4 1")
+       },
        {
                'compression_method' => 'zstd',
                'backup_flags' => [ '--compress', 'client-zstd:5' ],
index 781aaf99f38fe8ade5abc33167025890e622c67c..5f581d1de3769bdad98a39f2726c80cd0b8619cc 100644 (file)
@@ -322,9 +322,9 @@ astreamer_lz4_decompressor_content(astreamer *streamer,
 
        mystreamer = (astreamer_lz4_frame *) streamer;
        next_in = (uint8 *) data;
-       next_out = (uint8 *) mystreamer->base.bbs_buffer.data;
+       next_out = (uint8 *) mystreamer->base.bbs_buffer.data + mystreamer->bytes_written;
        avail_in = len;
-       avail_out = mystreamer->base.bbs_buffer.maxlen;
+       avail_out = mystreamer->base.bbs_buffer.maxlen - mystreamer->bytes_written;
 
        while (avail_in > 0)
        {