[libc-commits] [libc] [libc] Fix remaining 32-bit time syscalls on 32-bit platforms (PR #203009)
Jeff Bailey via libc-commits
libc-commits at lists.llvm.org
Wed Jun 10 08:28:39 PDT 2026
https://github.com/kaladron updated https://github.com/llvm/llvm-project/pull/203009
>From f20871a2f1d0a849b7e74e475b70f4c5c6e14af4 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Wed, 10 Jun 2026 11:13:29 +0100
Subject: [PATCH] [libc] Fix remaining 32-bit time syscalls on 32-bit platforms
After removing 32-bit time_t support, some time-related syscall wrappers
were still passing 32-bit timespec structures directly to 64-bit time
syscalls (like SYS_clock_nanosleep_time64, SYS_pselect6_time64, etc.).
This caused stack corruption and invalid arguments on 32-bit platforms.
Commit 961d5b8ac7de fixed this for clock_gettime. This commit applies
similar fixes to:
- clock_settime
- nanosleep (including correct handling of remaining time)
- select (pselect6)
- utimensat
- futex (wait timeout)
Also fixes a bug in select where a null timeout would incorrectly
result in passing a zero timeout (polling) instead of blocking.
---
.../OSUtil/linux/syscall_wrappers/utimensat.h | 28 +++++++++++++--
.../src/__support/threads/linux/futex_utils.h | 27 ++++++++++++++-
.../__support/time/linux/clock_settime.cpp | 23 +++++++++++--
libc/src/sys/select/linux/select.cpp | 30 +++++++++++++---
libc/src/time/linux/nanosleep.cpp | 34 +++++++++++++++++--
5 files changed, 128 insertions(+), 14 deletions(-)
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/utimensat.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/utimensat.h
index cbf79f5de2cbf..7fcec27aeb455 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/utimensat.h
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/utimensat.h
@@ -15,15 +15,37 @@
#include "src/__support/macros/config.h"
#include "hdr/types/struct_timespec.h"
-#include <sys/syscall.h> // For syscall numbers
+#include <sys/syscall.h>
+#if defined(SYS_utimensat_time64)
+#include <linux/time_types.h>
+#endif
namespace LIBC_NAMESPACE_DECL {
namespace linux_syscalls {
LIBC_INLINE ErrorOr<int> utimensat(int dirfd, const char *path,
- const struct timespec times[2], int flags) {
+ const timespec times[2], int flags) {
#if defined(SYS_utimensat_time64)
- int ret = syscall_impl<int>(SYS_utimensat_time64, dirfd, path, times, flags);
+ int ret;
+ // In overlay mode on 32-bit platforms, the system timespec (which we use)
+ // may be 32-bit (8 bytes), while the kernel's __kernel_timespec (used by
+ // _time64 syscalls) is 64-bit (16 bytes). If they match, we can pass the
+ // pointer directly. Otherwise, we must convert to avoid stack corruption.
+ if constexpr (sizeof(timespec) == sizeof(__kernel_timespec)) {
+ ret = syscall_impl<int>(SYS_utimensat_time64, dirfd, path, times, flags);
+ } else {
+ if (times != nullptr) {
+ __kernel_timespec ts64[2];
+ ts64[0].tv_sec = times[0].tv_sec;
+ ts64[0].tv_nsec = times[0].tv_nsec;
+ ts64[1].tv_sec = times[1].tv_sec;
+ ts64[1].tv_nsec = times[1].tv_nsec;
+ ret = syscall_impl<int>(SYS_utimensat_time64, dirfd, path, ts64, flags);
+ } else {
+ ret =
+ syscall_impl<int>(SYS_utimensat_time64, dirfd, path, nullptr, flags);
+ }
+ }
#elif defined(SYS_utimensat)
static_assert(
sizeof(timespec::tv_nsec) == sizeof(long),
diff --git a/libc/src/__support/threads/linux/futex_utils.h b/libc/src/__support/threads/linux/futex_utils.h
index ff6b5d526a3c1..6beed0eabe520 100644
--- a/libc/src/__support/threads/linux/futex_utils.h
+++ b/libc/src/__support/threads/linux/futex_utils.h
@@ -20,6 +20,9 @@
#include "src/__support/time/abs_timeout.h"
#include <linux/errno.h>
#include <linux/futex.h>
+#if defined(SYS_futex_time64)
+#include <linux/time_types.h>
+#endif
namespace LIBC_NAMESPACE_DECL {
@@ -52,12 +55,34 @@ class Futex : public cpp::Atomic<FutexWordType> {
if (this->load(cpp::MemoryOrder::RELAXED) != expected)
return 0;
+#if defined(SYS_futex_time64)
+ __kernel_timespec ts64{};
+ void *timeout_arg = nullptr;
+ if (timeout) {
+ // In overlay mode on 32-bit platforms, the system timespec (which we
+ // use) may be 32-bit (8 bytes), while the kernel's __kernel_timespec
+ // (used by _time64 syscalls) is 64-bit (16 bytes). If they match, we
+ // can pass the pointer directly. Otherwise, we must convert to avoid
+ // stack corruption.
+ if constexpr (sizeof(timespec) == sizeof(__kernel_timespec)) {
+ timeout_arg = const_cast<timespec *>(&timeout->get_timespec());
+ } else {
+ ts64.tv_sec = timeout->get_timespec().tv_sec;
+ ts64.tv_nsec = timeout->get_timespec().tv_nsec;
+ timeout_arg = &ts64;
+ }
+ }
+#else
+ void *timeout_arg =
+ timeout ? const_cast<timespec *>(&timeout->get_timespec()) : nullptr;
+#endif
+
int ret = syscall_impl<int>(
/*syscall_number=*/FUTEX_SYSCALL_ID,
/*futex_addr=*/this,
/*op=*/op,
/*expected=*/expected,
- /*timeout=*/timeout ? &timeout->get_timespec() : nullptr,
+ /*timeout=*/timeout_arg,
/*ignored=*/nullptr,
/*bitset=*/FUTEX_BITSET_MATCH_ANY);
diff --git a/libc/src/__support/time/linux/clock_settime.cpp b/libc/src/__support/time/linux/clock_settime.cpp
index ec96828b42352..a23374804b7eb 100644
--- a/libc/src/__support/time/linux/clock_settime.cpp
+++ b/libc/src/__support/time/linux/clock_settime.cpp
@@ -24,9 +24,26 @@ namespace internal {
ErrorOr<int> clock_settime(clockid_t clockid, const timespec *ts) {
int ret;
#if defined(SYS_clock_settime64)
- ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_clock_settime64,
- static_cast<long>(clockid),
- reinterpret_cast<long>(ts));
+ // In overlay mode on 32-bit platforms, the system timespec (which we use)
+ // may be 32-bit (8 bytes), while the kernel's __kernel_timespec (used by
+ // _time64 syscalls) is 64-bit (16 bytes). If they match, we can pass the
+ // pointer directly. Otherwise, we must convert to avoid stack corruption.
+ if constexpr (sizeof(timespec) == sizeof(__kernel_timespec)) {
+ ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_clock_settime64,
+ static_cast<long>(clockid),
+ reinterpret_cast<long>(ts));
+ } else {
+ __kernel_timespec ts64{};
+ __kernel_timespec *ts64_ptr = nullptr;
+ if (ts != nullptr) {
+ ts64.tv_sec = ts->tv_sec;
+ ts64.tv_nsec = ts->tv_nsec;
+ ts64_ptr = &ts64;
+ }
+ ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_clock_settime64,
+ static_cast<long>(clockid),
+ reinterpret_cast<long>(ts64_ptr));
+ }
#elif defined(SYS_clock_settime)
static_assert(
sizeof(timespec::tv_nsec) == sizeof(long),
diff --git a/libc/src/sys/select/linux/select.cpp b/libc/src/sys/select/linux/select.cpp
index 939cad4cd2bdd..4f6b45de170ee 100644
--- a/libc/src/sys/select/linux/select.cpp
+++ b/libc/src/sys/select/linux/select.cpp
@@ -17,7 +17,10 @@
#include "src/__support/macros/config.h"
#include "hdr/types/size_t.h"
-#include <sys/syscall.h> // For syscall numbers.
+#include <sys/syscall.h>
+#if defined(SYS_pselect6_time64)
+#include <linux/time_types.h>
+#endif
namespace LIBC_NAMESPACE_DECL {
@@ -55,8 +58,26 @@ LLVM_LIBC_FUNCTION(int, select,
}
pselect6_sigset_t pss{nullptr, sizeof(sigset_t)};
#if defined(SYS_pselect6_time64)
- int ret = LIBC_NAMESPACE::syscall_impl<int>(
- SYS_pselect6_time64, nfds, read_set, write_set, error_set, &ts, &pss);
+ int ret;
+ // In overlay mode on 32-bit platforms, the system timespec (which we use)
+ // may be 32-bit (8 bytes), while the kernel's __kernel_timespec (used by
+ // _time64 syscalls) is 64-bit (16 bytes). If they match, we can pass the
+ // pointer directly. Otherwise, we must convert to avoid stack corruption.
+ if constexpr (sizeof(timespec) == sizeof(__kernel_timespec)) {
+ ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_pselect6_time64, nfds, read_set,
+ write_set, error_set,
+ timeout ? &ts : nullptr, &pss);
+ } else {
+ __kernel_timespec ts64{};
+ __kernel_timespec *ts_ptr = nullptr;
+ if (timeout != nullptr) {
+ ts64.tv_sec = ts.tv_sec;
+ ts64.tv_nsec = ts.tv_nsec;
+ ts_ptr = &ts64;
+ }
+ ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_pselect6_time64, nfds, read_set,
+ write_set, error_set, ts_ptr, &pss);
+ }
#elif defined(SYS_pselect6)
static_assert(
sizeof(timespec::tv_nsec) == sizeof(long),
@@ -64,7 +85,8 @@ LLVM_LIBC_FUNCTION(int, select,
"matches the register size (long). It is unsafe on 32-bit platforms "
"with 64-bit tv_nsec.");
int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_pselect6, nfds, read_set,
- write_set, error_set, &ts, &pss);
+ write_set, error_set,
+ timeout ? &ts : nullptr, &pss);
#else
#error "SYS_pselect6 and SYS_pselect6_time64 syscalls not available."
#endif
diff --git a/libc/src/time/linux/nanosleep.cpp b/libc/src/time/linux/nanosleep.cpp
index 15b119ead20c6..9bbcc3b05ab96 100644
--- a/libc/src/time/linux/nanosleep.cpp
+++ b/libc/src/time/linux/nanosleep.cpp
@@ -13,15 +13,43 @@
#include "src/__support/common.h"
#include "src/__support/libc_errno.h"
#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
-#include <sys/syscall.h> // For syscall numbers.
+#include <sys/syscall.h>
+#if defined(SYS_clock_nanosleep_time64)
+#include <linux/time_types.h>
+#endif
namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(int, nanosleep, (const timespec *req, timespec *rem)) {
+ LIBC_CRASH_ON_NULLPTR(req);
#if defined(SYS_clock_nanosleep_time64)
- int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_clock_nanosleep_time64,
- CLOCK_REALTIME, 0, req, rem);
+ int ret;
+ // In overlay mode on 32-bit platforms, the system timespec (which we use)
+ // may be 32-bit (8 bytes), while the kernel's __kernel_timespec (used by
+ // _time64 syscalls) is 64-bit (16 bytes). If they match, we can pass the
+ // pointer directly. Otherwise, we must convert to avoid stack corruption.
+ if constexpr (sizeof(timespec) == sizeof(__kernel_timespec)) {
+ ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_clock_nanosleep_time64,
+ CLOCK_REALTIME, 0, req, rem);
+ } else {
+ __kernel_timespec ts64_req{};
+ __kernel_timespec *req_ptr = nullptr;
+ if (req != nullptr) {
+ ts64_req.tv_sec = req->tv_sec;
+ ts64_req.tv_nsec = req->tv_nsec;
+ req_ptr = &ts64_req;
+ }
+ __kernel_timespec ts64_rem{};
+ __kernel_timespec *rem_ptr = rem ? &ts64_rem : nullptr;
+ ret = LIBC_NAMESPACE::syscall_impl<int>(
+ SYS_clock_nanosleep_time64, CLOCK_REALTIME, 0, req_ptr, rem_ptr);
+ if (ret == -EINTR && rem != nullptr) {
+ rem->tv_sec = static_cast<decltype(rem->tv_sec)>(ts64_rem.tv_sec);
+ rem->tv_nsec = static_cast<decltype(rem->tv_nsec)>(ts64_rem.tv_nsec);
+ }
+ }
#elif defined(SYS_nanosleep)
static_assert(
sizeof(timespec::tv_nsec) == sizeof(long),
More information about the libc-commits
mailing list