]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Fix archive_time.c issues (concurrency, 32 bit) (#2563)
authorTobias Stoeckmann <stoeckmann@users.noreply.github.com>
Fri, 9 May 2025 11:33:32 +0000 (13:33 +0200)
committerGitHub <noreply@github.com>
Fri, 9 May 2025 11:33:32 +0000 (13:33 +0200)
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 <tobias@stoeckmann.org>
libarchive/archive_time.c

index f7a67b853108b0771543068094ea9dfe1e8b9813..3352c809bd11e8458a3810bc914d48fdb92f52e7 100644 (file)
 #include "archive_platform.h"
 #include "archive_private.h"
 #include "archive_time_private.h"
+#include <limits.h>
 #include <stdlib.h>
+#include <string.h>
 
 #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 <winnt.h>
@@ -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;
 }