[libc-commits] [libc] 81d8273 - [libc] Fix overflow check for 32-bit mktime. (#101993)

via libc-commits libc-commits at lists.llvm.org
Tue Aug 6 20:51:29 PDT 2024


Author: Simon Tatham
Date: 2024-08-06T20:51:25-07:00
New Revision: 81d8273978ace8b9a2b8cfd811b94e95f9560ac5

URL: https://github.com/llvm/llvm-project/commit/81d8273978ace8b9a2b8cfd811b94e95f9560ac5
DIFF: https://github.com/llvm/llvm-project/commit/81d8273978ace8b9a2b8cfd811b94e95f9560ac5.diff

LOG: [libc] Fix overflow check for 32-bit mktime. (#101993)

The 32-bit time_t rolls over to a value with its high bit set at
2038-01-19 03:14:07. The overflow check for this in mktime() was
checking each individual component of the time: reject if year>2038, or
if month>1, or if day>19, etc. As a result it would reject valid times
before the overflow point, because a low-order component was out of
range even though a higher-order one makes the time as a whole safe. The
earliest failing value is 2145916808 == 2038-01-01 00:00:08, in which
only the seconds field 'overflows'.

Fixed so that if any component is _less_ than its threshold value, we
don't check the lower-order components.

Added: 
    

Modified: 
    libc/src/time/mktime.cpp
    libc/test/src/time/mktime_test.cpp

Removed: 
    


################################################################################
diff  --git a/libc/src/time/mktime.cpp b/libc/src/time/mktime.cpp
index 9ea316d504f5b..72cd229120538 100644
--- a/libc/src/time/mktime.cpp
+++ b/libc/src/time/mktime.cpp
@@ -42,12 +42,18 @@ LLVM_LIBC_FUNCTION(time_t, mktime, (struct tm * tm_out)) {
       return time_utils::out_of_range();
     if (tm_out->tm_mday > 19)
       return time_utils::out_of_range();
-    if (tm_out->tm_hour > 3)
-      return time_utils::out_of_range();
-    if (tm_out->tm_min > 14)
-      return time_utils::out_of_range();
-    if (tm_out->tm_sec > 7)
-      return time_utils::out_of_range();
+    else if (tm_out->tm_mday == 19) {
+      if (tm_out->tm_hour > 3)
+        return time_utils::out_of_range();
+      else if (tm_out->tm_hour == 3) {
+        if (tm_out->tm_min > 14)
+          return time_utils::out_of_range();
+        else if (tm_out->tm_min == 14) {
+          if (tm_out->tm_sec > 7)
+            return time_utils::out_of_range();
+        }
+      }
+    }
   }
 
   // Years are ints.  A 32-bit year will fit into a 64-bit time_t.

diff  --git a/libc/test/src/time/mktime_test.cpp b/libc/test/src/time/mktime_test.cpp
index 3142a4f3d06dd..84e6c7eb2c42e 100644
--- a/libc/test/src/time/mktime_test.cpp
+++ b/libc/test/src/time/mktime_test.cpp
@@ -380,22 +380,115 @@ TEST(LlvmLibcMkTime, InvalidDays) {
 TEST(LlvmLibcMkTime, EndOf32BitEpochYear) {
   // Test for maximum value of a signed 32-bit integer.
   // Test implementation can encode time for Tue 19 January 2038 03:14:07 UTC.
-  struct tm tm_data {
-    .tm_sec = 7, .tm_min = 14, .tm_hour = 3, .tm_mday = 19,
-    .tm_mon = Month::JANUARY, .tm_year = tm_year(2038), .tm_wday = 0,
-    .tm_yday = 0, .tm_isdst = 0
-  };
-  EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data), Succeeds(0x7FFFFFFF));
-  EXPECT_TM_EQ((tm{.tm_sec = 7,
-                   .tm_min = 14,
-                   .tm_hour = 3,
-                   .tm_mday = 19,
-                   .tm_mon = Month::JANUARY,
-                   .tm_year = tm_year(2038),
-                   .tm_wday = 2,
-                   .tm_yday = 7,
-                   .tm_isdst = 0}),
-               tm_data);
+  {
+    struct tm tm_data {
+      .tm_sec = 7, .tm_min = 14, .tm_hour = 3, .tm_mday = 19,
+      .tm_mon = Month::JANUARY, .tm_year = tm_year(2038), .tm_wday = 0,
+      .tm_yday = 0, .tm_isdst = 0
+    };
+    EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data), Succeeds(0x7FFFFFFF));
+    EXPECT_TM_EQ((tm{.tm_sec = 7,
+                     .tm_min = 14,
+                     .tm_hour = 3,
+                     .tm_mday = 19,
+                     .tm_mon = Month::JANUARY,
+                     .tm_year = tm_year(2038),
+                     .tm_wday = 2,
+                     .tm_yday = 7,
+                     .tm_isdst = 0}),
+                 tm_data);
+  }
+
+  // Now test some times before that, to ensure they are not rejected.
+  {
+    // 2038-01-19 03:13:59 tests that even a large seconds field is
+    // accepted if the minutes field is smaller.
+    struct tm tm_data {
+      .tm_sec = 59, .tm_min = 13, .tm_hour = 3, .tm_mday = 19,
+      .tm_mon = Month::JANUARY, .tm_year = tm_year(2038), .tm_wday = 0,
+      .tm_yday = 0, .tm_isdst = 0
+    };
+    EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data), Succeeds(0x7FFFFFFF - 8));
+    EXPECT_TM_EQ((tm{.tm_sec = 59,
+                     .tm_min = 13,
+                     .tm_hour = 3,
+                     .tm_mday = 19,
+                     .tm_mon = Month::JANUARY,
+                     .tm_year = tm_year(2038),
+                     .tm_wday = 2,
+                     .tm_yday = 7,
+                     .tm_isdst = 0}),
+                 tm_data);
+  }
+
+  {
+    // 2038-01-19 02:59:59 tests that large seconds and minutes are
+    // accepted if the hours field is smaller.
+    struct tm tm_data {
+      .tm_sec = 59, .tm_min = 59, .tm_hour = 2, .tm_mday = 19,
+      .tm_mon = Month::JANUARY, .tm_year = tm_year(2038), .tm_wday = 0,
+      .tm_yday = 0, .tm_isdst = 0
+    };
+    EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data),
+                Succeeds(0x7FFFFFFF - 8 - 14 * TimeConstants::SECONDS_PER_MIN));
+    EXPECT_TM_EQ((tm{.tm_sec = 59,
+                     .tm_min = 59,
+                     .tm_hour = 2,
+                     .tm_mday = 19,
+                     .tm_mon = Month::JANUARY,
+                     .tm_year = tm_year(2038),
+                     .tm_wday = 2,
+                     .tm_yday = 7,
+                     .tm_isdst = 0}),
+                 tm_data);
+  }
+
+  {
+    // 2038-01-18 23:59:59 tests that large seconds, minutes and hours
+    // are accepted if the days field is smaller.
+    struct tm tm_data {
+      .tm_sec = 59, .tm_min = 59, .tm_hour = 23, .tm_mday = 18,
+      .tm_mon = Month::JANUARY, .tm_year = tm_year(2038), .tm_wday = 0,
+      .tm_yday = 0, .tm_isdst = 0
+    };
+    EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data),
+                Succeeds(0x7FFFFFFF - 8 - 14 * TimeConstants::SECONDS_PER_MIN -
+                         3 * TimeConstants::SECONDS_PER_HOUR));
+    EXPECT_TM_EQ((tm{.tm_sec = 59,
+                     .tm_min = 59,
+                     .tm_hour = 23,
+                     .tm_mday = 18,
+                     .tm_mon = Month::JANUARY,
+                     .tm_year = tm_year(2038),
+                     .tm_wday = 2,
+                     .tm_yday = 7,
+                     .tm_isdst = 0}),
+                 tm_data);
+  }
+
+  {
+    // 2038-01-18 23:59:59 tests that the final second of 2037 is
+    // accepted.
+    struct tm tm_data {
+      .tm_sec = 59, .tm_min = 59, .tm_hour = 23, .tm_mday = 31,
+      .tm_mon = Month::DECEMBER, .tm_year = tm_year(2037), .tm_wday = 0,
+      .tm_yday = 0, .tm_isdst = 0
+    };
+    EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data),
+                Succeeds(0x7FFFFFFF - 8 - 14 * TimeConstants::SECONDS_PER_MIN -
+                         3 * TimeConstants::SECONDS_PER_HOUR -
+                         18 * TimeConstants::SECONDS_PER_DAY));
+    EXPECT_TM_EQ((tm{.tm_sec = 59,
+                     .tm_min = 59,
+                     .tm_hour = 23,
+                     .tm_mday = 31,
+                     .tm_mon = Month::DECEMBER,
+                     .tm_year = tm_year(2037),
+                     .tm_wday = 2,
+                     .tm_yday = 7,
+                     .tm_isdst = 0}),
+                 tm_data);
+  }
 }
 
 TEST(LlvmLibcMkTime, Max64BitYear) {


        


More information about the libc-commits mailing list