libarchive/test/test_read_format_tar_pax_g_large.c \
libarchive/test/test_read_format_tar_pax_large_attr.c \
libarchive/test/test_read_format_tar_pax_negative_time.c \
+ libarchive/test/test_read_format_tar_pax_timestamps.c \
+ libarchive/test/test_read_format_tar_timestamp_overflow.c \
libarchive/test/test_read_format_tbz.c \
libarchive/test/test_read_format_tgz.c \
libarchive/test/test_read_format_tlz.c \
libarchive/test/test_read_format_tar_pax_g_large.tar.uu \
libarchive/test/test_read_format_tar_pax_large_attr.tar.Z.uu \
libarchive/test/test_read_format_tar_pax_negative_time.tar.uu \
+ libarchive/test/test_read_format_tar_pax_timestamps.tar.uu \
+ libarchive/test/test_read_format_tar_timestamp_overflow.tar.uu \
libarchive/test/test_read_format_ustar_filename_cp866.tar.Z.uu \
libarchive/test/test_read_format_ustar_filename_eucjp.tar.Z.uu \
libarchive/test/test_read_format_ustar_filename_koi8r.tar.Z.uu \
static const size_t acl_limit = 131072; /* Longest textual ACL: 128kiB */
static const int64_t entry_limit = 0xfffffffffffffffLL; /* 2^60 bytes = 1 ExbiByte */
+/*
+ * There's no standard for TIME_T_MAX. So we compute it
+ * here. TODO: Move this to configure time, but be careful
+ * about cross-compile environments.
+ */
+static int64_t
+get_time_t_max(void)
+{
+#if defined(TIME_T_MAX)
+ return TIME_T_MAX;
+#else
+ /* ISO C allows time_t to be a floating-point type,
+ but POSIX requires an integer type. The following
+ should work on any system that follows the POSIX
+ conventions. */
+ if (((time_t)0) < ((time_t)-1)) {
+ /* Time_t is unsigned */
+ return (~(time_t)0);
+ } else {
+ /* Time_t is signed. */
+ /* Assume it's the same as int64_t or int32_t */
+ if (sizeof(time_t) == sizeof(int64_t)) {
+ return (time_t)INT64_MAX;
+ } else {
+ return (time_t)INT32_MAX;
+ }
+ }
+#endif
+}
+
int
archive_read_support_format_gnutar(struct archive *a)
{
archive_entry_set_gid(entry, tar_atol(header->gid, sizeof(header->gid)));
}
if (!archive_entry_mtime_is_set(entry)) {
- archive_entry_set_mtime(entry, tar_atol(header->mtime, sizeof(header->mtime)), 0);
+ int64_t t64 = tar_atol(header->mtime, sizeof(header->mtime));
+ time_t t = (time_t)t64;
+ if ((int64_t)t != t64) { /* time_t overflowed */
+ t = get_time_t_max();
+ }
+ archive_entry_set_mtime(entry, t, 0);
}
/* Reconcile the size info. */
}
static int
-pax_attribute_read_time(struct archive_read *a, size_t value_length, int64_t *ps, long *pn, int64_t *unconsumed) {
+pax_attribute_read_time(struct archive_read *a, size_t value_length, __LA_TIME_T *ps, long *pn, int64_t *unconsumed) {
struct archive_string as;
int r;
return (r);
}
- pax_time(as.s, archive_strlen(&as), ps, pn);
+ int64_t sec = 0;
+ pax_time(as.s, archive_strlen(&as), &sec, pn);
archive_string_free(&as);
- if (*ps == INT64_MIN) {
+
+ if (sec == INT64_MIN) {
*ps = 0;
*pn = 0;
return (ARCHIVE_WARN);
+ } else {
+ *ps = (__LA_TIME_T)sec;
}
return (ARCHIVE_OK);
}
*/
if (key_length == 12 && memcmp(key, "creationtime", 12) == 0) {
/* LIBARCHIVE.creationtime */
- if ((err = pax_attribute_read_time(a, value_length, &t, &n, unconsumed)) == ARCHIVE_OK) {
- archive_entry_set_birthtime(entry, t, n);
+ __LA_TIME_T sec = 0;
+ if ((err = pax_attribute_read_time(a, value_length, &sec, &n, unconsumed)) == ARCHIVE_OK) {
+ archive_entry_set_birthtime(entry, sec, n);
+ } else {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC,
+ "Ignoring malformed pax creationtime");
}
return (err);
}
break;
case 'a':
if (key_length == 5 && memcmp(key, "atime", 5) == 0) {
- if ((err = pax_attribute_read_time(a, value_length, &t, &n, unconsumed)) == ARCHIVE_OK) {
- archive_entry_set_atime(entry, t, n);
+ __LA_TIME_T sec = 0;
+ if ((err = pax_attribute_read_time(a, value_length, &sec, &n, unconsumed)) == ARCHIVE_OK) {
+ archive_entry_set_atime(entry, sec, n);
+ } else {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC,
+ "Ignoring malformed pax atime");
}
return (err);
}
break;
case 'c':
if (key_length == 5 && memcmp(key, "ctime", 5) == 0) {
- if ((err = pax_attribute_read_time(a, value_length, &t, &n, unconsumed)) == ARCHIVE_OK) {
- archive_entry_set_ctime(entry, t, n);
+ __LA_TIME_T sec = 0;
+ if ((err = pax_attribute_read_time(a, value_length, &sec, &n, unconsumed)) == ARCHIVE_OK) {
+ archive_entry_set_ctime(entry, sec, n);
+ } else {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC,
+ "Ignoring malformed pax ctime");
}
return (err);
} else if (key_length == 7 && memcmp(key, "charset", 7) == 0) {
break;
case 'm':
if (key_length == 5 && memcmp(key, "mtime", 5) == 0) {
- if ((err = pax_attribute_read_time(a, value_length, &t, &n, unconsumed)) == ARCHIVE_OK) {
- archive_entry_set_mtime(entry, t, n);
+ __LA_TIME_T sec;
+ if ((err = pax_attribute_read_time(a, value_length, &sec, &n, unconsumed)) == ARCHIVE_OK) {
+ archive_entry_set_mtime(entry, sec, n);
+ } else {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC,
+ "Ignoring malformed pax mtime");
}
return (err);
}
/*
* Parse a decimal time value, which may include a fractional portion
*
- * Sets ps to INT64_MIN on error.
+ * Sets ps to INT64_MIN on error, including syntax issues such as non-digits,
+ * or a time value that's outside the range of time_t.
*/
static void
pax_time(const char *p, size_t length, int64_t *ps, long *pn)
*ps = s * sign;
+#if ARCHIVE_VERSION_NUMBER < 4000000
+ /* Libarchive 4.0 will have __LA_TIME_T == int64_t, so
+ this will be unnecessary. */
+ /* Test whether it overflows __LA_TIME_T */
+ __LA_TIME_T sec = (__LA_TIME_T)*ps;
+ if ((int64_t)sec != *ps) {
+ *ps = INT64_MIN;
+ *pn = 0;
+ return;
+ }
+#endif
+
/* Calculate nanoseconds. */
*pn = 0;
- if (length <= 0 || *p != '.')
+ if (length <= 0) {
return;
+ }
+
+ /* Skip `.` */
+ if (*p != '.') {
+ *ps = INT64_MIN;
+ *pn = 0;
+ return;
+ }
+ ++p;
+ --length;
l = 100000000UL;
do {
+ if (length <= 0) {
+ return;
+ }
+ if (*p >= '0' && *p <= '9') {
+ *pn += (*p - '0') * l;
+ } else {
+ *ps = INT64_MIN;
+ *pn = 0;
+ return;
+ }
++p;
--length;
- if (length > 0 && *p >= '0' && *p <= '9')
- *pn += (*p - '0') * l;
- else
- break;
} while (l /= 10);
+
+ /* Ignore resolution beyond nanoseconds,
+ but verify it's all decimal digits. */
+ while (length > 0) {
+ if (*p < '0' || *p > '9') {
+ *ps = INT64_MIN;
+ *pn = 0;
+ return;
+ }
+ ++p;
+ --length;
+ }
}
/*
test_read_format_tar_pax_g_large.c
test_read_format_tar_pax_large_attr.c
test_read_format_tar_pax_negative_time.c
+ test_read_format_tar_pax_timestamps.c
+ test_read_format_tar_timestamp_overflow.c
test_read_format_tbz.c
test_read_format_tgz.c
test_read_format_tlz.c
--- /dev/null
+/*-
+ * 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 has a negative modification time.
+ */
+DEFINE_TEST(test_read_format_tar_pax_timestamps)
+{
+ char name[] = "test_read_format_tar_pax_timestamps.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(224165920, archive_entry_mtime(ae));
+ assertEqualInt(0, archive_entry_mtime_nsec(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(0100644, 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));
+}
--- /dev/null
+begin 644 test_read_format_tar_pax_timestamps.tar
+M4&%X2&5A9&5R+V5M<'1Y````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````#`P,#8T-"``,#`P,#`P(``P,#`P,#`@`#`P,#`P,#`P,#,W
+M(#`P,#`P,#`P,#`P(#`Q-#(P-0`@>`````````````````````````!N````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````!U<W1A<@`P,')O;W0`
+M````````````````````````````````````<F]O=```````````````````
+M```````````````````P,#`P,#`@`#`P,#`P,"``````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````S,2!M=&EM93TQ-S0X,#@Y-#8T+CDU,3DR.#0V
+M-V$*````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````;@``````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````&5M<'1Y````````
+M````````````````````````````````````````````````````````````
+M```````````````````````````````````````````````````````````P
+M,#`V-#0@`#`P,#`P,"``,#`P,#`P(``P,#`P,#`P,#`P,""```````````U<
+M@"`P,3$U-S4`(#``````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````=7-T87(`,#!R;V]T````````````````
+M`````````````````````')O;W0`````````````````````````````````
+M````,#`P,#`P(``P,#`P,#`@````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+H````````````````````````````````````````````````````````
+`
+end
--- /dev/null
+/*-
+ * Copyright (c) 2026 Tim Kientzle
+ * 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 tar archive that has an oversized timestamp value.
+ * This should not cause an infinite loop.
+ */
+DEFINE_TEST(test_read_format_tar_timestamp_overflow)
+{
+ char name[] = "test_read_format_tar_timestamp_overflow.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_OK, archive_read_next_header(a, &ae));
+ assertEqualString("tmp/f.txt", archive_entry_pathname(ae));
+ /* 15155542402 should be parsed correctly if time_t is 64-bit,
+ * or handled gracefully if not. */
+ (void)archive_entry_mtime(ae);
+
+ /* Verify the end-of-archive. */
+ assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae));
+
+ assertEqualInt(ARCHIVE_OK, archive_read_close(a));
+ assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}
--- /dev/null
+begin 600 test_read_format_tar_timestamp_overflow.tar
+M=&UP+V8N='AT````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````#`P,#`V-``````````````````````````````````P,#$V
+M`#$U,34U-30R-#`R`#`Q,C8Q-``@,```````````````````````````````
+M``````````````````````````````#_````````````````````````````
+M``````````````````````````````````````````!U<W1A<B`@`&UA<W1E
+M<@``````````````````````````````````;6%S=&5R````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````````````````!`````````````````````#0`
+M,#`P,3<U,``P,#`Q-S4P`#`P,#`P,#``````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+&````````
+`
+end