From e6330bf497b7caf680f9fff993a7b363522ca90e Mon Sep 17 00:00:00 2001 From: Martin Matuska Date: Mon, 23 Sep 2024 11:46:43 +0200 Subject: [PATCH] Update vendor/libarchive to 3.7.6 Security fixes: #2330 iso: be more cautious about parsing ISO-9660 timestamps #2343 tar: clean up linkpath between entries Important bugfixes: #2338 tar: fix memory leaks when processing symlinks or parsing pax headers Obtained from: libarchive Vendor commit: f0a0bc6b3046e34c9d6981f8c026da51fea12c89 --- NEWS | 2 + build/version | 2 +- configure.ac | 4 +- libarchive/archive.h | 4 +- libarchive/archive_entry.h | 2 +- .../archive_read_support_format_iso9660.c | 186 ++++++++++++++++-- libarchive/archive_read_support_format_tar.c | 3 + libarchive/test/test_read_format_iso_Z.c | 12 +- tar/test/test_copy.c | 36 ++-- tar/write.c | 4 +- 10 files changed, 213 insertions(+), 42 deletions(-) diff --git a/NEWS b/NEWS index 1c46443fa3f..28303b2da84 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,5 @@ +Sep 23, 2024: libarchive 3.7.6 released + Sep 13, 2024: libarchive 3.7.5 released Apr 26, 2024: libarchive 3.7.4 released diff --git a/build/version b/build/version index 49ac2b56a17..be7fcfdc194 100644 --- a/build/version +++ b/build/version @@ -1 +1 @@ -3007005 +3007006 diff --git a/configure.ac b/configure.ac index 227275ab19a..226ae2c9aa1 100644 --- a/configure.ac +++ b/configure.ac @@ -4,8 +4,8 @@ dnl First, define all of the version numbers up front. dnl In particular, this allows the version macro to be used in AC_INIT dnl These first two version numbers are updated automatically on each release. -m4_define([LIBARCHIVE_VERSION_S],[3.7.5]) -m4_define([LIBARCHIVE_VERSION_N],[3007005]) +m4_define([LIBARCHIVE_VERSION_S],[3.7.6]) +m4_define([LIBARCHIVE_VERSION_N],[3007006]) dnl bsdtar and bsdcpio versioning tracks libarchive m4_define([BSDTAR_VERSION_S],LIBARCHIVE_VERSION_S()) diff --git a/libarchive/archive.h b/libarchive/archive.h index 8784ab77628..aeddc816a25 100644 --- a/libarchive/archive.h +++ b/libarchive/archive.h @@ -34,7 +34,7 @@ * assert that ARCHIVE_VERSION_NUMBER >= 2012108. */ /* Note: Compiler will complain if this does not match archive_entry.h! */ -#define ARCHIVE_VERSION_NUMBER 3007005 +#define ARCHIVE_VERSION_NUMBER 3007006 #include #include /* for wchar_t */ @@ -155,7 +155,7 @@ __LA_DECL int archive_version_number(void); /* * Textual name/version of the library, useful for version displays. */ -#define ARCHIVE_VERSION_ONLY_STRING "3.7.5" +#define ARCHIVE_VERSION_ONLY_STRING "3.7.6" #define ARCHIVE_VERSION_STRING "libarchive " ARCHIVE_VERSION_ONLY_STRING __LA_DECL const char * archive_version_string(void); diff --git a/libarchive/archive_entry.h b/libarchive/archive_entry.h index 3a0afffb08c..c054849be09 100644 --- a/libarchive/archive_entry.h +++ b/libarchive/archive_entry.h @@ -28,7 +28,7 @@ #define ARCHIVE_ENTRY_H_INCLUDED /* Note: Compiler will complain if this does not match archive.h! */ -#define ARCHIVE_VERSION_NUMBER 3007005 +#define ARCHIVE_VERSION_NUMBER 3007006 /* * Note: archive_entry.h is for use outside of libarchive; the diff --git a/libarchive/archive_read_support_format_iso9660.c b/libarchive/archive_read_support_format_iso9660.c index 056beb5ffdd..951afb6037e 100644 --- a/libarchive/archive_read_support_format_iso9660.c +++ b/libarchive/archive_read_support_format_iso9660.c @@ -273,7 +273,7 @@ struct file_info { char re; /* Having RRIP "RE" extension. */ char re_descendant; uint64_t cl_offset; /* Having RRIP "CL" extension. */ - int birthtime_is_set; + int time_is_set; /* Bitmask indicating which times are known */ time_t birthtime; /* File created time. */ time_t mtime; /* File last modified time. */ time_t atime; /* File last accessed time. */ @@ -306,6 +306,11 @@ struct file_info { } rede_files; }; +#define BIRTHTIME_IS_SET 1 +#define MTIME_IS_SET 2 +#define ATIME_IS_SET 4 +#define CTIME_IS_SET 8 + struct heap_queue { struct file_info **files; int allocated; @@ -394,7 +399,9 @@ static void dump_isodirrec(FILE *, const unsigned char *isodirrec); #endif static time_t time_from_tm(struct tm *); static time_t isodate17(const unsigned char *); +static int isodate17_valid(const unsigned char *); static time_t isodate7(const unsigned char *); +static int isodate7_valid(const unsigned char *); static int isBootRecord(struct iso9660 *, const unsigned char *); static int isVolumePartition(struct iso9660 *, const unsigned char *); static int isVDSetTerminator(struct iso9660 *, const unsigned char *); @@ -1351,13 +1358,22 @@ archive_read_format_iso9660_read_header(struct archive_read *a, archive_entry_set_uid(entry, file->uid); archive_entry_set_gid(entry, file->gid); archive_entry_set_nlink(entry, file->nlinks); - if (file->birthtime_is_set) + if ((file->time_is_set & BIRTHTIME_IS_SET)) archive_entry_set_birthtime(entry, file->birthtime, 0); else archive_entry_unset_birthtime(entry); - archive_entry_set_mtime(entry, file->mtime, 0); - archive_entry_set_ctime(entry, file->ctime, 0); - archive_entry_set_atime(entry, file->atime, 0); + if ((file->time_is_set & MTIME_IS_SET)) + archive_entry_set_mtime(entry, file->mtime, 0); + else + archive_entry_unset_mtime(entry); + if ((file->time_is_set & CTIME_IS_SET)) + archive_entry_set_ctime(entry, file->ctime, 0); + else + archive_entry_unset_ctime(entry); + if ((file->time_is_set & ATIME_IS_SET)) + archive_entry_set_atime(entry, file->atime, 0); + else + archive_entry_unset_atime(entry); /* N.B.: Rock Ridge supports 64-bit device numbers. */ archive_entry_set_rdev(entry, (dev_t)file->rdev); archive_entry_set_size(entry, iso9660->entry_bytes_remaining); @@ -1898,8 +1914,11 @@ parse_file_info(struct archive_read *a, struct file_info *parent, file->parent = parent; file->offset = offset; file->size = fsize; - file->mtime = isodate7(isodirrec + DR_date_offset); - file->ctime = file->atime = file->mtime; + if (isodate7_valid(isodirrec + DR_date_offset)) { + file->time_is_set |= MTIME_IS_SET | ATIME_IS_SET | CTIME_IS_SET; + file->mtime = isodate7(isodirrec + DR_date_offset); + file->ctime = file->atime = file->mtime; + } file->rede_files.first = NULL; file->rede_files.last = &(file->rede_files.first); @@ -2573,51 +2592,73 @@ parse_rockridge_TF1(struct file_info *file, const unsigned char *data, /* Use 17-byte time format. */ if ((flag & 1) && data_length >= 17) { /* Create time. */ - file->birthtime_is_set = 1; - file->birthtime = isodate17(data); + if (isodate17_valid(data)) { + file->time_is_set |= BIRTHTIME_IS_SET; + file->birthtime = isodate17(data); + } data += 17; data_length -= 17; } if ((flag & 2) && data_length >= 17) { /* Modify time. */ - file->mtime = isodate17(data); + if (isodate17_valid(data)) { + file->time_is_set |= MTIME_IS_SET; + file->mtime = isodate17(data); + } data += 17; data_length -= 17; } if ((flag & 4) && data_length >= 17) { /* Access time. */ - file->atime = isodate17(data); + if (isodate17_valid(data)) { + file->time_is_set |= ATIME_IS_SET; + file->atime = isodate17(data); + } data += 17; data_length -= 17; } if ((flag & 8) && data_length >= 17) { /* Attribute change time. */ - file->ctime = isodate17(data); + if (isodate17_valid(data)) { + file->time_is_set |= CTIME_IS_SET; + file->ctime = isodate17(data); + } } } else { /* Use 7-byte time format. */ if ((flag & 1) && data_length >= 7) { /* Create time. */ - file->birthtime_is_set = 1; - file->birthtime = isodate7(data); + if (isodate7_valid(data)) { + file->time_is_set |= BIRTHTIME_IS_SET; + file->birthtime = isodate7(data); + } data += 7; data_length -= 7; } if ((flag & 2) && data_length >= 7) { /* Modify time. */ - file->mtime = isodate7(data); + if (isodate7_valid(data)) { + file->time_is_set |= MTIME_IS_SET; + file->mtime = isodate7(data); + } data += 7; data_length -= 7; } if ((flag & 4) && data_length >= 7) { /* Access time. */ - file->atime = isodate7(data); + if (isodate7_valid(data)) { + file->time_is_set |= ATIME_IS_SET; + file->atime = isodate7(data); + } data += 7; data_length -= 7; } if ((flag & 8) && data_length >= 7) { /* Attribute change time. */ - file->ctime = isodate7(data); + if (isodate7_valid(data)) { + file->time_is_set |= CTIME_IS_SET; + file->ctime = isodate7(data); + } } } } @@ -3226,6 +3267,56 @@ isValid733Integer(const unsigned char *p) && p[3] == p[4]); } +static int +isodate7_valid(const unsigned char *v) +{ + int year = v[0]; + int month = v[1]; + int day = v[2]; + int hour = v[3]; + int minute = v[4]; + int second = v[5]; + int gmt_off = (signed char)v[6]; + + /* ECMA-119 9.1.5 "If all seven values are zero, it shall mean + * that the date is unspecified" */ + if (year == 0 + && month == 0 + && day == 0 + && hour == 0 + && minute == 0 + && second == 0 + && gmt_off == 0) + return 0; + /* + * Sanity-test each individual field + */ + /* Year can have any value */ + /* Month must be 1-12 */ + if (month < 1 || month > 12) + return 0; + /* Day must be 1-31 */ + if (day < 1 || day > 31) + return 0; + /* Hour must be 0-23 */ + if (hour > 23) + return 0; + /* Minute must be 0-59 */ + if (minute > 59) + return 0; + /* second must be 0-59 according to ECMA-119 9.1.5 */ + /* BUT: we should probably allow for the time being in UTC, which + allows up to 61 seconds in a minute in certain cases */ + if (second > 61) + return 0; + /* Offset from GMT must be -48 to +52 */ + if (gmt_off < -48 || gmt_off > +52) + return 0; + + /* All tests pass, this is OK */ + return 1; +} + static time_t isodate7(const unsigned char *v) { @@ -3252,6 +3343,67 @@ isodate7(const unsigned char *v) return (t); } +static int +isodate17_valid(const unsigned char *v) +{ + /* First 16 bytes are all ASCII digits */ + for (int i = 0; i < 16; i++) { + if (v[i] < '0' || v[i] > '9') + return 0; + } + + int year = (v[0] - '0') * 1000 + (v[1] - '0') * 100 + + (v[2] - '0') * 10 + (v[3] - '0'); + int month = (v[4] - '0') * 10 + (v[5] - '0'); + int day = (v[6] - '0') * 10 + (v[7] - '0'); + int hour = (v[8] - '0') * 10 + (v[9] - '0'); + int minute = (v[10] - '0') * 10 + (v[11] - '0'); + int second = (v[12] - '0') * 10 + (v[13] - '0'); + int hundredths = (v[14] - '0') * 10 + (v[15] - '0'); + int gmt_off = (signed char)v[16]; + + if (year == 0 && month == 0 && day == 0 + && hour == 0 && minute == 0 && second == 0 + && hundredths == 0 && gmt_off == 0) + return 0; + /* + * Sanity-test each individual field + */ + + /* Year must be 1900-2300 */ + /* (Not specified in ECMA-119, but these seem + like reasonable limits. */ + if (year < 1900 || year > 2300) + return 0; + /* Month must be 1-12 */ + if (month < 1 || month > 12) + return 0; + /* Day must be 1-31 */ + if (day < 1 || day > 31) + return 0; + /* Hour must be 0-23 */ + if (hour > 23) + return 0; + /* Minute must be 0-59 */ + if (minute > 59) + return 0; + /* second must be 0-59 according to ECMA-119 9.1.5 */ + /* BUT: we should probably allow for the time being in UTC, which + allows up to 61 seconds in a minute in certain cases */ + if (second > 61) + return 0; + /* Hundredths must be 0-99 */ + if (hundredths > 99) + return 0; + /* Offset from GMT must be -48 to +52 */ + if (gmt_off < -48 || gmt_off > +52) + return 0; + + /* All tests pass, this is OK */ + return 1; + +} + static time_t isodate17(const unsigned char *v) { diff --git a/libarchive/archive_read_support_format_tar.c b/libarchive/archive_read_support_format_tar.c index af601efba63..ab0b20a7e14 100644 --- a/libarchive/archive_read_support_format_tar.c +++ b/libarchive/archive_read_support_format_tar.c @@ -296,6 +296,7 @@ archive_read_format_tar_cleanup(struct archive_read *a) archive_string_free(&tar->entry_pathname_override); archive_string_free(&tar->entry_uname); archive_string_free(&tar->entry_gname); + archive_string_free(&tar->entry_linkpath); archive_string_free(&tar->line); archive_string_free(&tar->pax_global); archive_string_free(&tar->longname); @@ -726,6 +727,7 @@ tar_read_header(struct archive_read *a, struct tar *tar, archive_string_empty(&(tar->entry_pathname)); archive_string_empty(&(tar->entry_pathname_override)); archive_string_empty(&(tar->entry_uname)); + archive_string_empty(&tar->entry_linkpath); /* Ensure format is set. */ if (a->archive.archive_format_name == NULL) { @@ -1935,6 +1937,7 @@ header_pax_extension(struct archive_read *a, struct tar *tar, *unconsumed += 1; tar_flush_unconsumed(a, unconsumed); } + archive_string_free(&attr_name); *unconsumed += ext_size + ext_padding; /* diff --git a/libarchive/test/test_read_format_iso_Z.c b/libarchive/test/test_read_format_iso_Z.c index d07bc1bc883..716552fa36e 100644 --- a/libarchive/test/test_read_format_iso_Z.c +++ b/libarchive/test/test_read_format_iso_Z.c @@ -93,16 +93,20 @@ test_small(const char *name) assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); assertEqualString(".", archive_entry_pathname(ae)); - assertEqualIntA(a, 3443989665, archive_entry_atime(ae)); - assertEqualIntA(a, 0, archive_entry_birthtime(ae)); - assertEqualIntA(a, 3443989665, archive_entry_ctime(ae)); + assertEqualInt(0, archive_entry_atime_is_set(ae)); + assertEqualInt(0, archive_entry_atime(ae)); + assertEqualInt(0, archive_entry_birthtime_is_set(ae)); + assertEqualInt(0, archive_entry_birthtime(ae)); + assertEqualInt(0, archive_entry_ctime_is_set(ae)); + assertEqualInt(0, archive_entry_ctime(ae)); assertEqualIntA(a, 0, archive_entry_dev(ae)); assertEqualIntA(a, AE_IFDIR, archive_entry_filetype(ae)); assertEqualIntA(a, 0, archive_entry_gid(ae)); assertEqualStringA(a, NULL, archive_entry_gname(ae)); assertEqualIntA(a, 0, archive_entry_ino(ae)); assertEqualIntA(a, AE_IFDIR | 0700, archive_entry_mode(ae)); - assertEqualIntA(a, 3443989665, archive_entry_mtime(ae)); + assertEqualInt(0, archive_entry_mtime_is_set(ae)); + assertEqualInt(0, archive_entry_mtime(ae)); assertEqualIntA(a, 4, archive_entry_nlink(ae)); assertEqualIntA(a, 0700, archive_entry_perm(ae)); assertEqualIntA(a, 2048, archive_entry_size(ae)); diff --git a/tar/test/test_copy.c b/tar/test/test_copy.c index fd47a6d877e..27faaecbad6 100644 --- a/tar/test/test_copy.c +++ b/tar/test/test_copy.c @@ -190,7 +190,7 @@ create_tree(void) #define LIMIT_USTAR 100 static void -verify_tree(size_t limit) +verify_tree(size_t limit, const char *format) { char name1[260]; char name2[260]; @@ -203,6 +203,7 @@ verify_tree(size_t limit) /* Verify a file named "f/abcdef..." */ snprintf(name1, sizeof(name1), "f/%s", filenames[i]); if (i <= limit) { + failure("Verifying %s", format); assertFileExists(name1); assertFileContents(name1, (int)strlen(name1), name1); } @@ -210,6 +211,7 @@ verify_tree(size_t limit) snprintf(name2, sizeof(name2), "l/%s", filenames[i]); if (i + 2 <= limit) { /* Verify hardlink "l/abcdef..." */ + failure("Verifying %s", format); assertIsHardlink(name1, name2); /* Verify hardlink "m/abcdef..." */ name2[0] = 'm'; @@ -220,13 +222,16 @@ verify_tree(size_t limit) /* Verify symlink "s/abcdef..." */ snprintf(name1, sizeof(name1), "s/%s", filenames[i]); snprintf(name2, sizeof(name2), "../f/%s", filenames[i]); - if (strlen(name2) <= limit) + if (strlen(name2) <= limit) { + failure("Verifying %s", format); assertIsSymlink(name1, name2, 0); + } } /* Verify dir "d/abcdef...". */ snprintf(name1, sizeof(name1), "d/%s", filenames[i]); if (i + 1 <= limit) { /* +1 for trailing slash */ + failure("Verifying %s", format); if (assertIsDir(name1, -1)) { /* TODO: opendir/readdir this * directory and make sure @@ -246,7 +251,7 @@ verify_tree(size_t limit) char dir[2]; dir[0] = *dp; dir[1] = '\0'; d = opendir(dir); - failure("Unable to open dir '%s'", dir); + failure("Unable to open dir '%s' for testing %s", dir, format); if (!assert(d != NULL)) continue; while ((de = readdir(d)) != NULL) { @@ -278,25 +283,25 @@ verify_tree(size_t limit) } static void -copy_basic(void) +copy_basic(const char *extra_args, const char *name) { int r; /* NOTE: for proper operation on cygwin-1.5 and windows, the - * length of the name of the directory below, "plain", must be + * length of the name of the directory below must be * less than or equal to the length of the name of the original * directory, "original" This restriction derives from the * extremely limited pathname lengths on those platforms. */ - assertMakeDir("plain", 0775); - assertEqualInt(0, chdir("plain")); + assertMakeDir(name, 0775); + assertEqualInt(0, chdir(name)); /* * Use the tar program to create an archive. */ - r = systemf("%s cf archive -C ../original f d l m s >pack.out 2>pack.err", - testprog); - failure("Error invoking \"%s cf\"", testprog); + r = systemf("%s cf archive %s -C ../original f d l m s >pack.out 2>pack.err", + testprog, extra_args); + failure("Error invoking \"%s cf archive %s\"", testprog, extra_args); assertEqualInt(r, 0); /* Verify that nothing went to stdout or stderr. */ @@ -314,7 +319,7 @@ copy_basic(void) assertEmptyFile("unpack.err"); assertEmptyFile("unpack.out"); - verify_tree(LIMIT_NONE); + verify_tree(LIMIT_NONE, name); assertEqualInt(0, chdir("..")); } @@ -357,8 +362,8 @@ copy_ustar(void) assertEmptyFile("unpack.err"); assertEmptyFile("unpack.out"); - verify_tree(LIMIT_USTAR); - assertEqualInt(0, chdir("../..")); + verify_tree(LIMIT_USTAR, "ustar"); + assertEqualInt(0, chdir("..")); } DEFINE_TEST(test_copy) @@ -367,8 +372,11 @@ DEFINE_TEST(test_copy) create_tree(); /* Create sample files in "original" dir. */ /* Test simple "tar -c | tar -x" pipeline copy. */ - copy_basic(); + copy_basic("", "default"); /* Same, but constrain to ustar format. */ copy_ustar(); + + /* Same, but with pax format. */ + copy_basic(" --format pax", "pax"); } diff --git a/tar/write.c b/tar/write.c index 1d1139f4cde..43cd7023339 100644 --- a/tar/write.c +++ b/tar/write.c @@ -32,7 +32,9 @@ #ifdef HAVE_SYS_STAT_H #include #endif -#ifdef HAVE_ATTR_XATTR_H +#if HAVE_SYS_XATTR_H +#include +#elif HAVE_ATTR_XATTR_H #include #endif #ifdef HAVE_ERRNO_H