From: Tobias Stoeckmann Date: Fri, 9 May 2025 11:33:32 +0000 (+0200) Subject: Fix archive_time.c issues (concurrency, 32 bit) (#2563) X-Git-Tag: v3.8.0~31 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=1d7eadf4179320d229dd4f407a36567419a53193;p=thirdparty%2Flibarchive.git Fix archive_time.c issues (concurrency, 32 bit) (#2563) The refactoring of https://github.com/libarchive/libarchive/pull/2553 introduced three issues: 1. Introduction of a modifiable global static variable This violates the goal of having no global variables as stated in [the README.md](https://github.com/libarchive/libarchive/blob/b6f6557abb8235f604eced6facb42da8c7ab2a41/README.md?plain=1#L195) which in turn leads to concurrency issues. Without any form of mutex protection, multiple threads are not guaranteed to see the correct min/max values. Since these are not needed in regular use cases but only in edge cases, handle them in functions with local variables only. Also the global variables are locale-dependent which can change during runtime. In that case, future calls leads to issues. 2. Broken 32 bit support The writers for zip and others affected by the previously mentioned PR and test-suite on Debian 12 i686 are broken, because the calculation of maximum MS-DOS time is not possible with a 32 bit time_t. Treat these cases properly. 3. Edge case protection Huge or tiny int64_t values can easily lead to unsigned integer overflows. While these do not affect stability of libarchive, the results are still wrong, i.e. are not capped at min/max as expected. In total, the functions are much closer to their original versions again (+ more range checks). --------- Signed-off-by: Tobias Stoeckmann --- diff --git a/libarchive/archive_time.c b/libarchive/archive_time.c index f7a67b853..3352c809b 100644 --- a/libarchive/archive_time.c +++ b/libarchive/archive_time.c @@ -26,18 +26,15 @@ #include "archive_platform.h" #include "archive_private.h" #include "archive_time_private.h" +#include #include +#include #define NTFS_EPOC_TIME ARCHIVE_LITERAL_ULL(11644473600) #define NTFS_TICKS ARCHIVE_LITERAL_ULL(10000000) #define NTFS_EPOC_TICKS (NTFS_EPOC_TIME * NTFS_TICKS) #define DOS_MIN_TIME 0x00210000U #define DOS_MAX_TIME 0xff9fbf7dU -/* The min/max DOS Unix time are locale-dependant, so they're static variables, - * initialised on first use. */ -static char dos_initialised = 0; -static int64_t dos_max_unix; -static int64_t dos_min_unix; #if defined(_WIN32) && !defined(__CYGWIN__) #include @@ -58,9 +55,12 @@ dos_to_unix(uint32_t dos_time) { uint16_t msTime, msDate; struct tm ts; + time_t t; msTime = (0xFFFF & dos_time); msDate = (dos_time >> 16); + + memset(&ts, 0, sizeof(ts)); ts.tm_year = ((msDate >> 9) & 0x7f) + 80; /* Years since 1900. */ ts.tm_mon = ((msDate >> 5) & 0x0f) - 1; /* Month number. */ ts.tm_mday = msDate & 0x1f; /* Day of month. */ @@ -68,7 +68,8 @@ dos_to_unix(uint32_t dos_time) ts.tm_min = (msTime >> 5) & 0x3f; ts.tm_sec = (msTime << 1) & 0x3e; ts.tm_isdst = -1; - return mktime(&ts); + t = mktime(&ts); + return (int64_t)(t == (time_t)-1 ? INT32_MAX : t); } /* Convert into MSDOS-style date/time. */ @@ -82,52 +83,81 @@ unix_to_dos(int64_t unix_time) struct tm tmbuf; #endif - if (!dos_initialised) { - dos_max_unix = dos_to_unix(DOS_MAX_TIME); - dos_min_unix = dos_to_unix(DOS_MIN_TIME); - dos_initialised = 1; - } - if (unix_time >= dos_max_unix) { - return DOS_MAX_TIME; - } - else if(unix_time <= dos_min_unix) { - return DOS_MIN_TIME; + if (sizeof(time_t) < sizeof(int64_t) && (int64_t)ut != unix_time) { + ut = (time_t)(unix_time > 0 ? INT32_MAX : INT32_MIN); } - else { + #if defined(HAVE_LOCALTIME_S) - t = localtime_s(&tmbuf, &ut) ? NULL : &tmbuf; + t = localtime_s(&tmbuf, &ut) ? NULL : &tmbuf; #elif defined(HAVE_LOCALTIME_R) - t = localtime_r(&ut, &tmbuf); + t = localtime_r(&ut, &tmbuf); #else - t = localtime(&ut); + t = localtime(&ut); #endif - dt = 0; - dt += ((t->tm_year - 80) & 0x7f) << 9; - dt += ((t->tm_mon + 1) & 0x0f) << 5; - dt += (t->tm_mday & 0x1f); - dt <<= 16; - dt += (t->tm_hour & 0x1f) << 11; - dt += (t->tm_min & 0x3f) << 5; - dt += (t->tm_sec & 0x3e) >> 1; /* Only counting every 2 seconds. */ - return dt; + dt = 0; + if (t != NULL && t->tm_year >= INT_MIN + 80) { + const int year = t->tm_year - 80; + + if (year & ~0x7f) { + dt = year > 0 ? DOS_MAX_TIME : DOS_MIN_TIME; + } + else { + dt += (year & 0x7f) << 9; + dt += ((t->tm_mon + 1) & 0x0f) << 5; + dt += (t->tm_mday & 0x1f); + dt <<= 16; + dt += (t->tm_hour & 0x1f) << 11; + dt += (t->tm_min & 0x3f) << 5; + /* Only counting every 2 seconds. */ + dt += (t->tm_sec & 0x3e) >> 1; + } + } + if (dt > DOS_MAX_TIME) { + dt = DOS_MAX_TIME; } + else if (dt < DOS_MIN_TIME) { + dt = DOS_MIN_TIME; + } + return dt; } -/* Convert NTFS time to Unix sec/ncse */ +/* Convert NTFS time to Unix sec/nsec */ void ntfs_to_unix(uint64_t ntfs, int64_t* secs, uint32_t* nsecs) { - ntfs -= NTFS_EPOC_TICKS; - lldiv_t tdiv = lldiv(ntfs, NTFS_TICKS); - *secs = tdiv.quot; - *nsecs = tdiv.rem * 100; + if (ntfs > INT64_MAX) { + ntfs -= NTFS_EPOC_TICKS; + *secs = ntfs / NTFS_TICKS; + *nsecs = 100 * (ntfs % NTFS_TICKS); + } + else { + lldiv_t tdiv; + int64_t value = (int64_t)ntfs - (int64_t)NTFS_EPOC_TICKS; + + tdiv = lldiv(value, NTFS_TICKS); + *secs = tdiv.quot; + *nsecs = (uint32_t)(tdiv.rem * 100); + } } /* Convert Unix sec/nsec to NTFS time */ uint64_t unix_to_ntfs(int64_t secs, uint32_t nsecs) { - uint64_t ntfs = secs + NTFS_EPOC_TIME; + uint64_t ntfs; + + if (secs < -(int64_t)NTFS_EPOC_TIME) + return 0; + + ntfs = secs + NTFS_EPOC_TIME; + + if (ntfs > UINT64_MAX / NTFS_TICKS) + return UINT64_MAX; + ntfs *= NTFS_TICKS; + + if (ntfs > UINT64_MAX - nsecs/100) + return UINT64_MAX; + return ntfs + nsecs/100; }