-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[libc] Fix overflow check for 32-bit mktime. #101993
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[libc] Fix overflow check for 32-bit mktime. #101993
Conversation
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.
@llvm/pr-subscribers-libc Author: Simon Tatham (statham-arm) ChangesThe 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. Full diff: https://github.com/llvm/llvm-project/pull/101993.diff 2 Files Affected:
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) {
|
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.