From: Tobias Stoeckmann Date: Sun, 25 May 2025 10:03:55 +0000 (+0200) Subject: tar: Keep block alignment after pax error X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=101646e34e88a1eeb407bcff6db784aa12b37839;p=thirdparty%2Flibarchive.git tar: Keep block alignment after pax error If a pax attribute has a 0 length value and no newline, the tar reader gets out of sync with block alignment. This happens because the pax parser assumes that variable value_length (which includes the terminating newline) is at least 1. To get the real value length, 1 is subtracted. This result is subtracted from extsize, which in this case would lead to `extsize -= -1`, i.e. the remaining byte count is increased. Such an unexpected calculation leads to an off-by-one when skipping to the next block. In supplied test case, bsdtar complains that the checksum of the next block is wrong. Since the tar parser was not properly 512 bytes aligned, this is no surprise. Gracefully handle such a case like GNU tar does and warn the user that an invalid attribute has been encountered. Signed-off-by: Tobias Stoeckmann --- diff --git a/Makefile.am b/Makefile.am index d159f2970..22253d709 100644 --- a/Makefile.am +++ b/Makefile.am @@ -566,6 +566,7 @@ libarchive_test_SOURCES= \ libarchive/test/test_read_format_zip_zip64.c \ libarchive/test/test_read_format_zip_with_invalid_traditional_eocd.c \ libarchive/test/test_read_large.c \ + libarchive/test/test_read_pax_empty_val_no_nl.c \ libarchive/test/test_read_pax_xattr_rht_security_selinux.c \ libarchive/test/test_read_pax_xattr_schily.c \ libarchive/test/test_read_pax_truncated.c \ @@ -1043,6 +1044,7 @@ libarchive_test_EXTRA_DIST=\ libarchive/test/test_read_large_splitted_rar_ac.uu \ libarchive/test/test_read_large_splitted_rar_ad.uu \ libarchive/test/test_read_large_splitted_rar_ae.uu \ + libarchive/test/test_read_pax_empty_val_no_nl.tar.uu \ libarchive/test/test_read_pax_xattr_rht_security_selinux.tar.uu \ libarchive/test/test_read_pax_xattr_schily.tar.uu \ libarchive/test/test_read_splitted_rar_aa.uu \ diff --git a/libarchive/archive_read_support_format_tar.c b/libarchive/archive_read_support_format_tar.c index 4ead50c7d..1cc667169 100644 --- a/libarchive/archive_read_support_format_tar.c +++ b/libarchive/archive_read_support_format_tar.c @@ -2001,6 +2001,13 @@ header_pax_extension(struct archive_read *a, struct tar *tar, *unconsumed += p - attr_start; tar_flush_unconsumed(a, unconsumed); + if (value_length == 0) { + archive_set_error(&a->archive, EINVAL, + "Malformed pax attributes"); + *unconsumed += ext_size + ext_padding; + return (ARCHIVE_WARN); + } + /* pax_attribute will consume value_length - 1 */ r = pax_attribute(a, tar, entry, attr_name.s, archive_strlen(&attr_name), value_length - 1, unconsumed); ext_size -= value_length - 1; diff --git a/libarchive/test/CMakeLists.txt b/libarchive/test/CMakeLists.txt index 2fa8b01c8..1443f1c9d 100644 --- a/libarchive/test/CMakeLists.txt +++ b/libarchive/test/CMakeLists.txt @@ -209,6 +209,7 @@ IF(ENABLE_TEST) test_read_format_zip_zip64.c test_read_format_zip_with_invalid_traditional_eocd.c test_read_large.c + test_read_pax_empty_val_no_nl.c test_read_pax_xattr_rht_security_selinux.c test_read_pax_xattr_schily.c test_read_pax_truncated.c diff --git a/libarchive/test/test_read_pax_empty_val_no_nl.c b/libarchive/test/test_read_pax_empty_val_no_nl.c new file mode 100644 index 000000000..f98548875 --- /dev/null +++ b/libarchive/test/test_read_pax_empty_val_no_nl.c @@ -0,0 +1,65 @@ +/*- + * Copyright (c) 2025 Tobias Stoeckmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" + +/* + * Read a pax formatted tar archive that contains an invalid attribute, + * because it does not end in a newline. Additionally, value is empty. + * The pax reader should stop and tar reader should continue with warning. + */ +DEFINE_TEST(test_read_pax_empty_val_no_nl) +{ + char name[] = "test_read_pax_empty_val_no_nl.tar"; + struct archive_entry *ae; + struct archive *a; + + assert((a = archive_read_new()) != NULL); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a)); + extract_reference_file(name); + assertEqualIntA(a, ARCHIVE_OK, archive_read_open_filename(a, name, 10240)); + + /* Read first entry. */ + assertEqualIntA(a, ARCHIVE_WARN, archive_read_next_header(a, &ae)); + assertEqualString("empty", archive_entry_pathname(ae)); + assertEqualInt(1748163748, archive_entry_mtime(ae)); + assertEqualInt(0, archive_entry_uid(ae)); + assertEqualString("root", archive_entry_uname(ae)); + assertEqualInt(0, archive_entry_gid(ae)); + assertEqualString("root", archive_entry_gname(ae)); + assertEqualInt(0100600, archive_entry_mode(ae)); + assertEqualInt(archive_entry_is_encrypted(ae), 0); + assertEqualIntA(a, archive_read_has_encrypted_entries(a), ARCHIVE_READ_FORMAT_ENCRYPTION_UNSUPPORTED); + + /* Verify the end-of-archive. */ + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + + /* Verify that the format detection worked. */ + assertEqualInt(archive_filter_code(a, 0), ARCHIVE_FILTER_NONE); + assertEqualInt(archive_format(a), ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE); + + assertEqualInt(ARCHIVE_OK, archive_read_close(a)); + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); +} diff --git a/libarchive/test/test_read_pax_empty_val_no_nl.tar.uu b/libarchive/test/test_read_pax_empty_val_no_nl.tar.uu new file mode 100644 index 000000000..5de8b25ec --- /dev/null +++ b/libarchive/test/test_read_pax_empty_val_no_nl.tar.uu @@ -0,0 +1,60 @@ +begin 600 test_read_pax_empty_val_no_nl.tar +M4&%X2&5A9&5R+V5M<'1Y```````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````#`P,#8P,"``,#`P,#`P(``P,#`P,#`@`#`P,#`P,#`P,#`T +M(#$U,#$T-34V,C0T(#`Q-#`U-@`@>``````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````!U