From eec65c761bb406beccf674e371ea38b231136707 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Tue, 26 Apr 2022 11:50:23 +0100 Subject: [PATCH] gthread: Fix futex timespec type on 32-bit kernels with 64-bit userspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `struct timespec` type documented as being passed to the `futex()` syscall actually needs to be the *kernel’s* timespec type. This will be a different width from the userspace timespec type if running a 64-bit userspace on a 32-bit kernel. That mismatch will cause `g_cond_wait_until()` to return `FALSE` immediately. No other uses of `futex()` in GLib use the timeout argument, so they’re all OK. Following a detailed suggestion by Rich Felker, pass a different timespec type into `futex()` if `__NR_futex_time64` is defined. That’s the 64-bit time version of `futex()` which was added in kernel 5.1, and which was only added for 32-bit kernels. Signed-off-by: Philip Withnall Fixes: #2634 --- glib/gthread-posix.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/glib/gthread-posix.c b/glib/gthread-posix.c index 8e2e66db5..8c403f975 100644 --- a/glib/gthread-posix.c +++ b/glib/gthread-posix.c @@ -1599,6 +1599,13 @@ g_cond_wait_until (GCond *cond, { struct timespec now; struct timespec span; +#ifdef __NR_futex_time64 + long span_arg[2]; + G_STATIC_ASSERT (sizeof (span_arg[0]) == 4); +#else + struct timespec span_arg; +#endif + guint sampled; int res; gboolean success; @@ -1618,9 +1625,33 @@ g_cond_wait_until (GCond *cond, if (span.tv_sec < 0) return FALSE; + /* On x32 (ILP32 ABI on x86_64) and potentially sparc64, the raw futex() + * syscall takes a 32-bit timespan argument *regardless* of whether userspace + * is using 32-bit or 64-bit `struct timespec`. This means that we can’t + * unconditionally pass a `struct timespec` pointer into the syscall. + * + * Assume that any such platform is new enough to define the + * `__NR_futex_time64` workaround syscall (which accepts 64-bit timespecs, + * introduced in kernel 5.1), and use that as a proxy for whether to pass in + * `long[2]` or `struct timespec`. + * + * As per https://lwn.net/Articles/776427/, the `time64` syscalls only exist + * on 32-bit platforms, so in this case `sizeof(long)` should always be + * 32 bits. + * + * Don’t bother actually calling `__NR_futex_time64` as the `span` is relative + * and hence very unlikely to overflow, even if using 32-bit longs. + */ +#ifdef __NR_futex_time64 + span_arg[0] = span.tv_sec; + span_arg[1] = span.tv_nsec; +#else + span_arg = span; +#endif + sampled = cond->i[0]; g_mutex_unlock (mutex); - res = syscall (__NR_futex, &cond->i[0], (gsize) FUTEX_WAIT_PRIVATE, (gsize) sampled, &span); + res = syscall (__NR_futex, &cond->i[0], (gsize) FUTEX_WAIT_PRIVATE, (gsize) sampled, &span_arg); success = (res < 0 && errno == ETIMEDOUT) ? FALSE : TRUE; g_mutex_lock (mutex);