Skip to content

Commit e7e0eee

Browse files
committed
libstdc++: Avoid 32-bit time_t overflows in futex calls
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. The conversion to a relative timeout is done in two places, so this factors it into a new function and adds the overflow checks there. 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) (__atomic_futex_unsigned_base::_M_futex_wait_until_steady): Use relative_timespec.
1 parent 0d1189b commit e7e0eee

File tree

1 file changed

+53
-26
lines changed

1 file changed

+53
-26
lines changed

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

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include <unistd.h>
3232
#include <sys/time.h>
3333
#include <errno.h>
34+
#include <ext/numeric_traits.h>
3435
#include <debug/debug.h>
3536

3637
#ifdef _GLIBCXX_USE_CLOCK_GETTIME_SYSCALL
@@ -46,20 +47,55 @@ const unsigned futex_clock_realtime_flag = 256;
4647
const unsigned futex_bitset_match_any = ~0;
4748
const unsigned futex_wake_op = 1;
4849

50+
namespace std _GLIBCXX_VISIBILITY(default)
51+
{
52+
_GLIBCXX_BEGIN_NAMESPACE_VERSION
53+
4954
namespace
5055
{
5156
std::atomic<bool> futex_clock_realtime_unavailable;
5257
std::atomic<bool> futex_clock_monotonic_unavailable;
53-
}
5458

55-
namespace std _GLIBCXX_VISIBILITY(default)
56-
{
57-
_GLIBCXX_BEGIN_NAMESPACE_VERSION
59+
// Return the relative duration from (now_s + now_ns) to (abs_s + abs_ns)
60+
// as a timespec.
61+
struct timespec
62+
relative_timespec(chrono::seconds abs_s, chrono::nanoseconds abs_ns,
63+
time_t now_s, long now_ns)
64+
{
65+
struct timespec rt;
66+
67+
// Did we already time out?
68+
if (now_s > abs_s.count())
69+
{
70+
rt.tv_sec = -1;
71+
return rt;
72+
}
73+
74+
auto rel_s = abs_s.count() - now_s;
75+
76+
// Avoid overflows
77+
if (rel_s > __gnu_cxx::__int_traits<time_t>::__max)
78+
rel_s = __gnu_cxx::__int_traits<time_t>::__max;
79+
else if (rel_s < __gnu_cxx::__int_traits<time_t>::__min)
80+
rel_s = __gnu_cxx::__int_traits<time_t>::__min;
81+
82+
// Convert the absolute timeout value to a relative timeout
83+
rt.tv_sec = rel_s;
84+
rt.tv_nsec = abs_ns.count() - now_ns;
85+
if (rt.tv_nsec < 0)
86+
{
87+
rt.tv_nsec += 1000000000;
88+
--rt.tv_sec;
89+
}
90+
91+
return rt;
92+
}
93+
} // namespace
5894

5995
bool
60-
__atomic_futex_unsigned_base::_M_futex_wait_until(unsigned *__addr,
61-
unsigned __val,
62-
bool __has_timeout, chrono::seconds __s, chrono::nanoseconds __ns)
96+
__atomic_futex_unsigned_base::
97+
_M_futex_wait_until(unsigned *__addr, unsigned __val, bool __has_timeout,
98+
chrono::seconds __s, chrono::nanoseconds __ns)
6399
{
64100
if (!__has_timeout)
65101
{
@@ -109,15 +145,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
109145
// true or has just been set to true.
110146
struct timeval tv;
111147
gettimeofday (&tv, NULL);
148+
112149
// Convert the absolute timeout value to a relative timeout
113-
struct timespec rt;
114-
rt.tv_sec = __s.count() - tv.tv_sec;
115-
rt.tv_nsec = __ns.count() - tv.tv_usec * 1000;
116-
if (rt.tv_nsec < 0)
117-
{
118-
rt.tv_nsec += 1000000000;
119-
--rt.tv_sec;
120-
}
150+
auto rt = relative_timespec(__s, __ns, tv.tv_sec, tv.tv_usec * 1000);
151+
121152
// Did we already time out?
122153
if (rt.tv_sec < 0)
123154
return false;
@@ -134,9 +165,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
134165
}
135166

136167
bool
137-
__atomic_futex_unsigned_base::_M_futex_wait_until_steady(unsigned *__addr,
138-
unsigned __val,
139-
bool __has_timeout, chrono::seconds __s, chrono::nanoseconds __ns)
168+
__atomic_futex_unsigned_base::
169+
_M_futex_wait_until_steady(unsigned *__addr, unsigned __val,
170+
bool __has_timeout,
171+
chrono::seconds __s, chrono::nanoseconds __ns)
140172
{
141173
if (!__has_timeout)
142174
{
@@ -188,15 +220,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
188220
#else
189221
clock_gettime(CLOCK_MONOTONIC, &ts);
190222
#endif
223+
191224
// Convert the absolute timeout value to a relative timeout
192-
struct timespec rt;
193-
rt.tv_sec = __s.count() - ts.tv_sec;
194-
rt.tv_nsec = __ns.count() - ts.tv_nsec;
195-
if (rt.tv_nsec < 0)
196-
{
197-
rt.tv_nsec += 1000000000;
198-
--rt.tv_sec;
199-
}
225+
auto rt = relative_timespec(__s, __ns, ts.tv_sec, ts.tv_nsec);
226+
200227
// Did we already time out?
201228
if (rt.tv_sec < 0)
202229
return false;

0 commit comments

Comments
 (0)