[libc-commits] [libc] [libc] Fix select tv_nsec conversion and tm_year overflow (PR #200425)

via libc-commits libc-commits at lists.llvm.org
Fri May 29 08:33:14 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libc

Author: Jeff Bailey (kaladron)

<details>
<summary>Changes</summary>

Fixed two pre-existing bugs in time-related functions:

* sys/select: Fix tv_nsec calculation in select.cpp during timeout normalization. Previously, when timeout->tv_usec was >= 1000000, the overflow seconds were added to tv_sec but the entire tv_usec value was still converted to tv_nsec. This double-counted the overflow, resulting in an incorrect timeout, and caused tv_nsec to exceed 10^9 which made the kernel fail with EINVAL. Fixed by modulo-ing tv_usec by 1000000 before converting to nanoseconds.

* time: Tighten the years bounds check in time_utils.cpp to account for the 100-year offset before casting to 32-bit signed int tm_year. This prevents an integer wrap-around/overflow bug for years close to INT_MAX.

Added unit tests to demonstrate and verify both fixes.

Assisted-by: Automated tooling, human reviewed.

---
Full diff: https://github.com/llvm/llvm-project/pull/200425.diff


4 Files Affected:

- (modified) libc/src/sys/select/linux/select.cpp (+1-1) 
- (modified) libc/src/time/time_utils.cpp (+2-1) 
- (modified) libc/test/src/sys/select/select_failure_test.cpp (+25) 
- (modified) libc/test/src/time/gmtime_test.cpp (+16) 


``````````diff
diff --git a/libc/src/sys/select/linux/select.cpp b/libc/src/sys/select/linux/select.cpp
index 6c434eb584596..97264287434c9 100644
--- a/libc/src/sys/select/linux/select.cpp
+++ b/libc/src/sys/select/linux/select.cpp
@@ -50,7 +50,7 @@ LLVM_LIBC_FUNCTION(int, select,
       ts.tv_nsec = 999999999;
     } else {
       ts.tv_sec = timeout->tv_sec + timeout->tv_usec / 1000000;
-      ts.tv_nsec = timeout->tv_usec * 1000;
+      ts.tv_nsec = (timeout->tv_usec % 1000000) * 1000;
     }
   }
   pselect6_sigset_t pss{nullptr, sizeof(sigset_t)};
diff --git a/libc/src/time/time_utils.cpp b/libc/src/time/time_utils.cpp
index 1d0daea6b321e..45ba0e8ce2793 100644
--- a/libc/src/time/time_utils.cpp
+++ b/libc/src/time/time_utils.cpp
@@ -217,7 +217,8 @@ int64_t update_from_seconds(time_t total_seconds, tm *tm) {
     years++;
   }
 
-  if (years > INT_MAX || years < INT_MIN)
+  constexpr int64_t YEAR_OFFSET = 2000 - time_constants::TIME_YEAR_BASE; // 100
+  if (years > INT_MAX - YEAR_OFFSET || years < INT_MIN - YEAR_OFFSET)
     return time_utils::out_of_range();
 
   // All the data (years, month and remaining days) was calculated from
diff --git a/libc/test/src/sys/select/select_failure_test.cpp b/libc/test/src/sys/select/select_failure_test.cpp
index c5a7ad7a11a35..0b26aa3508837 100644
--- a/libc/test/src/sys/select/select_failure_test.cpp
+++ b/libc/test/src/sys/select/select_failure_test.cpp
@@ -27,3 +27,28 @@ TEST_F(LlvmLibcSelectTest, SelectInvalidFD) {
   ASSERT_THAT(LIBC_NAMESPACE::select(-1, &set, nullptr, nullptr, &timeout),
               Fails(EINVAL));
 }
+
+TEST_F(LlvmLibcSelectTest, SelectAcceptsLargeMicroseconds) {
+  int pipe_fds[2];
+  ASSERT_EQ(0, ::pipe(pipe_fds));
+
+  fd_set read_set;
+  FD_ZERO(&read_set);
+  FD_SET(pipe_fds[0], &read_set);
+
+  struct timeval timeout{
+      0, 1000000 // 1 second (will be normalized)
+  };
+
+  // Write to pipe so select returns immediately.
+  ASSERT_EQ(1, static_cast<int>(::write(pipe_fds[1], "a", 1)));
+
+  // select should return 1 (ready) immediately, not fail with EINVAL.
+  int ret = LIBC_NAMESPACE::select(pipe_fds[0] + 1, &read_set, nullptr, nullptr,
+                                   &timeout);
+  ASSERT_EQ(1, ret);
+
+  // Cleanup
+  ::close(pipe_fds[0]);
+  ::close(pipe_fds[1]);
+}
diff --git a/libc/test/src/time/gmtime_test.cpp b/libc/test/src/time/gmtime_test.cpp
index 1df768ac721fd..fbc6112a1582e 100644
--- a/libc/test/src/time/gmtime_test.cpp
+++ b/libc/test/src/time/gmtime_test.cpp
@@ -39,6 +39,22 @@ TEST_F(LlvmLibcGmTime, OutOfRange) {
   ASSERT_ERRNO_EQ(EOVERFLOW);
 }
 
+TEST_F(LlvmLibcGmTime, OverflowYear) {
+  if (sizeof(time_t) < sizeof(int64_t))
+    return;
+
+  // Test for year close to INT_MAX that would overflow tm_year (int)
+  // after adding the 100-year offset.
+  constexpr int64_t SECONDS_PER_AVERAGE_YEAR = 31556952;
+  time_t seconds =
+      LIBC_NAMESPACE::time_constants::SECONDS_UNTIL2000_MARCH_FIRST +
+      (static_cast<int64_t>(INT_MAX) - 50) * SECONDS_PER_AVERAGE_YEAR;
+
+  struct tm *tm_data = LIBC_NAMESPACE::gmtime(&seconds);
+  EXPECT_TRUE(tm_data == nullptr);
+  ASSERT_ERRNO_EQ(EOVERFLOW);
+}
+
 TEST_F(LlvmLibcGmTime, InvalidSeconds) {
   time_t seconds = 0;
   struct tm *tm_data = nullptr;

``````````

</details>


https://github.com/llvm/llvm-project/pull/200425


More information about the libc-commits mailing list