]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Fix issue when skipping first file in 7zip archive that is a multiple of 65536 bytes...
authorDuncan Horn <40036384+dunhor@users.noreply.github.com>
Thu, 20 Jun 2024 03:15:13 +0000 (20:15 -0700)
committerGitHub <noreply@github.com>
Thu, 20 Jun 2024 03:15:13 +0000 (20:15 -0700)
We noticed an issue where we had an archive that, if you skipped the
first entry and tried to extract the second, you'd get a failure saying
`Truncated 7-Zip file body`. Turns out that this is because the first
file in the archive is a multiple of 65,536 bytes (the size of the
uncompressed buffer) and therefore after `read_stream` skipped all of
the first file, `uncompressed_buffer_bytes_remaining` was set to zero
(because all data was consumed) and then it calls
`get_uncompressed_data` with `minimum` set to zero. This then saw that
`minimum > zip->uncompressed_buffer_bytes_remaining` evaluated to false,
causing us to read zero bytes, which got interpreted as a truncated
archive.

The fix here is simple: we now always call `extract_pack_stream` when
`uncompressed_buffer_bytes_remaining` is zero before exiting the
skipping loop.

Makefile.am
libarchive/archive_read_support_format_7zip.c
libarchive/test/test_read_format_7zip.c
libarchive/test/test_read_format_7zip_extract_second.7z.uu [new file with mode: 0644]

index 7560b14fe7eb2e17522531874d6fe1da140cf3fd..532b367c23cdd30f0b2253781d46c14d5de16611 100644 (file)
@@ -789,6 +789,7 @@ libarchive_test_EXTRA_DIST=\
        libarchive/test/test_read_format_7zip_encryption.7z.uu \
        libarchive/test/test_read_format_7zip_encryption_header.7z.uu \
        libarchive/test/test_read_format_7zip_encryption_partially.7z.uu \
+       libarchive/test/test_read_format_7zip_extract_second.7z.uu \
        libarchive/test/test_read_format_7zip_lzma1.7z.uu \
        libarchive/test/test_read_format_7zip_lzma1_2.7z.uu \
        libarchive/test/test_read_format_7zip_lzma1_lzma2.7z.uu \
index 634521d9521623bb4e3af8e932d043e887a1f554..e322808e7320f87ae6f0428b60e10e4a5de82577 100644 (file)
@@ -3462,7 +3462,7 @@ read_stream(struct archive_read *a, const void **buff, size_t size,
        /*
         * Skip the bytes we already has skipped in skip_stream().
         */
-       while (skip_bytes) {
+       while (1) {
                ssize_t skipped;
 
                if (zip->uncompressed_buffer_bytes_remaining == 0) {
@@ -3482,6 +3482,10 @@ read_stream(struct archive_read *a, const void **buff, size_t size,
                                return (ARCHIVE_FATAL);
                        }
                }
+
+               if (!skip_bytes)
+                       break;
+
                skipped = get_uncompressed_data(
                        a, buff, (size_t)skip_bytes, 0);
                if (skipped < 0)
index cff82f2c3db39732de7f406d5b4f935c72780097..bb47be668286ed133f63667d785b1e1c96d6f20b 100644 (file)
@@ -1257,5 +1257,47 @@ DEFINE_TEST(test_read_format_7zip_win_attrib)
        assertEqualString("system", archive_entry_fflags_text(ae));
 
 
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}
+
+DEFINE_TEST(test_read_format_7zip_extract_second)
+{
+       struct archive *a;
+       char buffer[256];
+
+       assert((a = archive_read_new()) != NULL);
+
+       if (ARCHIVE_OK != archive_read_support_filter_lzma(a)) {
+               skipping(
+                   "7zip:lzma decoding is not supported on this platform");
+               assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+               return;
+       }
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+
+       /*
+        * The test archive has two files: first.txt which is a 65,536 file (the
+        * size of the uncompressed buffer), and second.txt which has contents
+        * we will validate. This test ensures we can skip first.txt and still
+        * be able to read the contents of second.txt
+        */
+       const char *refname = "test_read_format_7zip_extract_second.7z";
+       extract_reference_file(refname);
+
+       assertEqualIntA(a, ARCHIVE_OK,
+               archive_read_open_filename(a, refname, 10240));
+
+       struct archive_entry *ae;
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualString("first.txt", archive_entry_pathname(ae));
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualString("second.txt", archive_entry_pathname(ae));
+
+       assertEqualInt(23, archive_read_data(a, buffer, sizeof(buffer)));
+       assertEqualMem("This is from second.txt", buffer, 23);
+
        assertEqualInt(ARCHIVE_OK, archive_read_free(a));
 }
diff --git a/libarchive/test/test_read_format_7zip_extract_second.7z.uu b/libarchive/test/test_read_format_7zip_extract_second.7z.uu
new file mode 100644 (file)
index 0000000..aa3d6e2
--- /dev/null
@@ -0,0 +1,11 @@
+begin 644 test_read_format_7zip_extract_second.7z\r
+M-WJ\KR<<``-N%=VX!@$````````B`````````*R\U.<`&`Q"DFIGO`[1,RO\\r
+MN,RA7-QU1L&_]O_/$0MMLIEBUR3'BDX@M2C-5'VG./-4,5@W3Q@*__^7_,[H\r
+MEO`DB'[ZI>@H2_E>/W.2G$$.P01-X!YN5";SS[3#7Z4Q1G/EF.0'^D*[S8&8\r
+M[FV9DYX7,SA%^.Q\'?__P!@`````@3,'K@_4WV/Q0A7VLXG$X?GH4=5W^`UM\r
+M$N_EX$)LE*?K$W5?WLP:X0T[Q%V^?A!0E\VZRBB,)(MO`C`LO[O!3(1YL)<:\r
+MJ."`';WU;>GP5',%Z=6?*/H9*Z)&\*!2^<F\P&>,RV`R30UOBH8+5.;;2IKF\r
+M0W://&'?"L?0L2!)`*]F30B0&/_'<4``%P9Z`0F`C``'"P$``2,#`0$%70``\r
+-@``,@*@*`6]FB2D`````\r
+`\r
+end\r