Skip to content

Commit 81d8273

Browse files
authored
[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.
1 parent ee870e5 commit 81d8273

File tree

2 files changed

+121
-22
lines changed

2 files changed

+121
-22
lines changed

libc/src/time/mktime.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,18 @@ LLVM_LIBC_FUNCTION(time_t, mktime, (struct tm * tm_out)) {
4242
return time_utils::out_of_range();
4343
if (tm_out->tm_mday > 19)
4444
return time_utils::out_of_range();
45-
if (tm_out->tm_hour > 3)
46-
return time_utils::out_of_range();
47-
if (tm_out->tm_min > 14)
48-
return time_utils::out_of_range();
49-
if (tm_out->tm_sec > 7)
50-
return time_utils::out_of_range();
45+
else if (tm_out->tm_mday == 19) {
46+
if (tm_out->tm_hour > 3)
47+
return time_utils::out_of_range();
48+
else if (tm_out->tm_hour == 3) {
49+
if (tm_out->tm_min > 14)
50+
return time_utils::out_of_range();
51+
else if (tm_out->tm_min == 14) {
52+
if (tm_out->tm_sec > 7)
53+
return time_utils::out_of_range();
54+
}
55+
}
56+
}
5157
}
5258

5359
// Years are ints. A 32-bit year will fit into a 64-bit time_t.

libc/test/src/time/mktime_test.cpp

Lines changed: 109 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -380,22 +380,115 @@ TEST(LlvmLibcMkTime, InvalidDays) {
380380
TEST(LlvmLibcMkTime, EndOf32BitEpochYear) {
381381
// Test for maximum value of a signed 32-bit integer.
382382
// Test implementation can encode time for Tue 19 January 2038 03:14:07 UTC.
383-
struct tm tm_data {
384-
.tm_sec = 7, .tm_min = 14, .tm_hour = 3, .tm_mday = 19,
385-
.tm_mon = Month::JANUARY, .tm_year = tm_year(2038), .tm_wday = 0,
386-
.tm_yday = 0, .tm_isdst = 0
387-
};
388-
EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data), Succeeds(0x7FFFFFFF));
389-
EXPECT_TM_EQ((tm{.tm_sec = 7,
390-
.tm_min = 14,
391-
.tm_hour = 3,
392-
.tm_mday = 19,
393-
.tm_mon = Month::JANUARY,
394-
.tm_year = tm_year(2038),
395-
.tm_wday = 2,
396-
.tm_yday = 7,
397-
.tm_isdst = 0}),
398-
tm_data);
383+
{
384+
struct tm tm_data {
385+
.tm_sec = 7, .tm_min = 14, .tm_hour = 3, .tm_mday = 19,
386+
.tm_mon = Month::JANUARY, .tm_year = tm_year(2038), .tm_wday = 0,
387+
.tm_yday = 0, .tm_isdst = 0
388+
};
389+
EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data), Succeeds(0x7FFFFFFF));
390+
EXPECT_TM_EQ((tm{.tm_sec = 7,
391+
.tm_min = 14,
392+
.tm_hour = 3,
393+
.tm_mday = 19,
394+
.tm_mon = Month::JANUARY,
395+
.tm_year = tm_year(2038),
396+
.tm_wday = 2,
397+
.tm_yday = 7,
398+
.tm_isdst = 0}),
399+
tm_data);
400+
}
401+
402+
// Now test some times before that, to ensure they are not rejected.
403+
{
404+
// 2038-01-19 03:13:59 tests that even a large seconds field is
405+
// accepted if the minutes field is smaller.
406+
struct tm tm_data {
407+
.tm_sec = 59, .tm_min = 13, .tm_hour = 3, .tm_mday = 19,
408+
.tm_mon = Month::JANUARY, .tm_year = tm_year(2038), .tm_wday = 0,
409+
.tm_yday = 0, .tm_isdst = 0
410+
};
411+
EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data), Succeeds(0x7FFFFFFF - 8));
412+
EXPECT_TM_EQ((tm{.tm_sec = 59,
413+
.tm_min = 13,
414+
.tm_hour = 3,
415+
.tm_mday = 19,
416+
.tm_mon = Month::JANUARY,
417+
.tm_year = tm_year(2038),
418+
.tm_wday = 2,
419+
.tm_yday = 7,
420+
.tm_isdst = 0}),
421+
tm_data);
422+
}
423+
424+
{
425+
// 2038-01-19 02:59:59 tests that large seconds and minutes are
426+
// accepted if the hours field is smaller.
427+
struct tm tm_data {
428+
.tm_sec = 59, .tm_min = 59, .tm_hour = 2, .tm_mday = 19,
429+
.tm_mon = Month::JANUARY, .tm_year = tm_year(2038), .tm_wday = 0,
430+
.tm_yday = 0, .tm_isdst = 0
431+
};
432+
EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data),
433+
Succeeds(0x7FFFFFFF - 8 - 14 * TimeConstants::SECONDS_PER_MIN));
434+
EXPECT_TM_EQ((tm{.tm_sec = 59,
435+
.tm_min = 59,
436+
.tm_hour = 2,
437+
.tm_mday = 19,
438+
.tm_mon = Month::JANUARY,
439+
.tm_year = tm_year(2038),
440+
.tm_wday = 2,
441+
.tm_yday = 7,
442+
.tm_isdst = 0}),
443+
tm_data);
444+
}
445+
446+
{
447+
// 2038-01-18 23:59:59 tests that large seconds, minutes and hours
448+
// are accepted if the days field is smaller.
449+
struct tm tm_data {
450+
.tm_sec = 59, .tm_min = 59, .tm_hour = 23, .tm_mday = 18,
451+
.tm_mon = Month::JANUARY, .tm_year = tm_year(2038), .tm_wday = 0,
452+
.tm_yday = 0, .tm_isdst = 0
453+
};
454+
EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data),
455+
Succeeds(0x7FFFFFFF - 8 - 14 * TimeConstants::SECONDS_PER_MIN -
456+
3 * TimeConstants::SECONDS_PER_HOUR));
457+
EXPECT_TM_EQ((tm{.tm_sec = 59,
458+
.tm_min = 59,
459+
.tm_hour = 23,
460+
.tm_mday = 18,
461+
.tm_mon = Month::JANUARY,
462+
.tm_year = tm_year(2038),
463+
.tm_wday = 2,
464+
.tm_yday = 7,
465+
.tm_isdst = 0}),
466+
tm_data);
467+
}
468+
469+
{
470+
// 2038-01-18 23:59:59 tests that the final second of 2037 is
471+
// accepted.
472+
struct tm tm_data {
473+
.tm_sec = 59, .tm_min = 59, .tm_hour = 23, .tm_mday = 31,
474+
.tm_mon = Month::DECEMBER, .tm_year = tm_year(2037), .tm_wday = 0,
475+
.tm_yday = 0, .tm_isdst = 0
476+
};
477+
EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data),
478+
Succeeds(0x7FFFFFFF - 8 - 14 * TimeConstants::SECONDS_PER_MIN -
479+
3 * TimeConstants::SECONDS_PER_HOUR -
480+
18 * TimeConstants::SECONDS_PER_DAY));
481+
EXPECT_TM_EQ((tm{.tm_sec = 59,
482+
.tm_min = 59,
483+
.tm_hour = 23,
484+
.tm_mday = 31,
485+
.tm_mon = Month::DECEMBER,
486+
.tm_year = tm_year(2037),
487+
.tm_wday = 2,
488+
.tm_yday = 7,
489+
.tm_isdst = 0}),
490+
tm_data);
491+
}
399492
}
400493

401494
TEST(LlvmLibcMkTime, Max64BitYear) {

0 commit comments

Comments
 (0)