]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Avoid 32-bit time_t overflows in futex calls
authorJonathan Wakely <jwakely@redhat.com>
Fri, 13 Nov 2020 15:19:04 +0000 (15:19 +0000)
committerJonathan Wakely <jwakely@redhat.com>
Mon, 16 Nov 2020 21:13:52 +0000 (21:13 +0000)
The existing code doesn't check whether the chrono::seconds value is out
of range of time_t. When using a timeout before the epoch (with a
negative value) subtracting the current time (as time_t) and then
assigning it to a time_t can overflow to a large positive value. This
means that we end up waiting several years even though the specific
timeout was in the distant past.

We do have a check for negative timeouts, but that happens after the
conversion to time_t so happens after the overflow.

libstdc++-v3/ChangeLog:

* src/c++11/futex.cc (relative_timespec): New function to
create relative time from two absolute times.
(__atomic_futex_unsigned_base::_M_futex_wait_until): Use
relative_timespec.

(cherry picked from commit b54bd045ae908b8ace5c50ce1bdc8d472d48a514)

libstdc++-v3/src/c++11/futex.cc

index c9de11a7ec77784297b4dd7271a2e55ff9e3bded..5d68dafa052ec7efe194ac6869ffee00cff06cdd 100644 (file)
@@ -31,6 +31,7 @@
 #include <unistd.h>
 #include <sys/time.h>
 #include <errno.h>
+#include <ext/numeric_traits.h>
 #include <debug/debug.h>
 
 // Constants for the wait/wake futex syscall operations
@@ -41,10 +42,48 @@ namespace std _GLIBCXX_VISIBILITY(default)
 {
 _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
+namespace
+{
+  // Return the relative duration from (now_s + now_ns) to (abs_s + abs_ns)
+  // as a timespec.
+  struct timespec
+  relative_timespec(chrono::seconds abs_s, chrono::nanoseconds abs_ns,
+                   time_t now_s, long now_ns)
+  {
+    struct timespec rt;
+
+    // Did we already time out?
+    if (now_s > abs_s.count())
+      {
+       rt.tv_sec = -1;
+       return rt;
+      }
+
+    auto rel_s = abs_s.count() - now_s;
+
+    // Avoid overflows
+    if (rel_s > __gnu_cxx::__int_traits<time_t>::__max)
+      rel_s = __gnu_cxx::__int_traits<time_t>::__max;
+    else if (rel_s < __gnu_cxx::__int_traits<time_t>::__min)
+      rel_s = __gnu_cxx::__int_traits<time_t>::__min;
+
+    // Convert the absolute timeout value to a relative timeout
+    rt.tv_sec = rel_s;
+    rt.tv_nsec = abs_ns.count() - now_ns;
+    if (rt.tv_nsec < 0)
+      {
+       rt.tv_nsec += 1000000000;
+       --rt.tv_sec;
+      }
+
+    return rt;
+  }
+} // namespace
+
   bool
-  __atomic_futex_unsigned_base::_M_futex_wait_until(unsigned *__addr,
-      unsigned __val,
-      bool __has_timeout, chrono::seconds __s, chrono::nanoseconds __ns)
+  __atomic_futex_unsigned_base::
+  _M_futex_wait_until(unsigned *__addr, unsigned __val, bool __has_timeout,
+                     chrono::seconds __s, chrono::nanoseconds __ns)
   {
     if (!__has_timeout)
       {
@@ -60,15 +99,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       {
        struct timeval tv;
        gettimeofday (&tv, NULL);
+
        // Convert the absolute timeout value to a relative timeout
-       struct timespec rt;
-       rt.tv_sec = __s.count() - tv.tv_sec;
-       rt.tv_nsec = __ns.count() - tv.tv_usec * 1000;
-       if (rt.tv_nsec < 0)
-         {
-           rt.tv_nsec += 1000000000;
-           --rt.tv_sec;
-         }
+       auto rt = relative_timespec(__s, __ns, tv.tv_sec, tv.tv_usec * 1000);
+
        // Did we already time out?
        if (rt.tv_sec < 0)
          return false;