[v2,4/7] y2038: linux: Provide __clock_settime64 implementation

Message ID 20190429104613.16209-5-lukma@denx.de
State New
Headers show
Series
  • y2038: Linux: Provide __clock_* functions supporting 64 bit time
Related show

Commit Message

Lukasz Majewski April 29, 2019, 10:46 a.m.
This patch provides new __clock_settime64 explicit 64 bit function for
setting the time. Moreover, a 32 bit version - __clock_settime has been
refactored to internally use __clock_settime64.

The __clock_settime is now supposed to be used on 32 bit systems -
hence the necessary checks and conversion to 64 bit type. After this
change it is intrinsically Y2038 safe.

The new 64 bit syscall (clock_settime64) available from Linux
5.1+ has been used when applicable on 32 bit systems.

The __ASSUME_64BIT_TIME flag indicates if the Linux kernel provides 64 bit
version of clock_settime (i.e. clock_settime64). If defined - return value
is returned unconditionally. If not - the 32 bit version of this syscall
is executed instead.

When working on 32 bit systems without Y2038 time support the clock_settime
returns error when one wants to set time with wrong (overflowed) tv_sec
value. Moreover, the correctness of tv_nsec is checked.

In this patch the internal padding (tv_pad) of struct __timespec64 is set to
zero (on 32 bit systems) to avoid passing random data to Linux kernel.

The execution path on 64 bit systems has not been changed or affected in
any way.

Tests:
- The code has been tested with x86_64/x86 (native compilation):
make PARALLELMFLAGS="-j8" && make xcheck PARALLELMFLAGS="-j8"

- Run specific tests on ARM/x86 32bit systems (qemu):
https://github.com/lmajewski/meta-y2038
and run tests:
https://github.com/lmajewski/y2038-tests/commits/master
on kernels with and without 64 bit time support.

No regressions were observed.

* include/time.h (__clock_settime64):
  Add __clock_settime alias according to __TIMESIZE define
* sysdeps/unix/sysv/linux/clock_settime.c (__clock_settime):
  Refactor this function to be used only on 32 bit machines as a wrapper
  on __clock_settime64.
* sysdeps/unix/sysv/linux/clock_settime.c (__clock_settime64): Add
* sysdeps/unix/sysv/linux/clock_settime.c (__clock_settime64):
  Use clock_settime64 kernel syscall (available from 5.1-rc1+ Linux) by
  32 bit Y2038 safe systems

---
Changes for v2:
- Add support for __ASSUME_64BIT_TIME flag when Linux kernel provides syscalls
  supporting 64 bit time on 32 bit systems
- Provide fallback to 32 bit version of clock_settime when clock_settime64
  is not available
- Do not copy *tp to timespec - this seems like an overkill as in clock_settime()
  the 32 bit struct timespec is copied to internal 64 bit struct __timespec64
---
 include/time.h                          |  8 ++++++
 sysdeps/unix/sysv/linux/clock_settime.c | 50 +++++++++++++++++++++++++++++++--
 2 files changed, 55 insertions(+), 3 deletions(-)

-- 
2.11.0

Patch

diff --git a/include/time.h b/include/time.h
index 9827d2f045..7f08559072 100644
--- a/include/time.h
+++ b/include/time.h
@@ -132,6 +132,14 @@  extern struct tm *__gmtime64_r (const __time64_t *__restrict __timer,
 libc_hidden_proto (__gmtime64_r);
 #endif
 
+#if __TIMESIZE == 64
+# define __clock_settime64 __clock_settime
+#else
+extern int __clock_settime64 (clockid_t clock_id,
+                              const struct __timespec64 *tp);
+libc_hidden_proto (__clock_settime64)
+#endif
+
 /* Compute the `struct tm' representation of T,
    offset OFFSET seconds east of UTC,
    and store year, yday, mon, mday, wday, hour, min, sec into *TP.
diff --git a/sysdeps/unix/sysv/linux/clock_settime.c b/sysdeps/unix/sysv/linux/clock_settime.c
index d837e3019c..8037ab0eb1 100644
--- a/sysdeps/unix/sysv/linux/clock_settime.c
+++ b/sysdeps/unix/sysv/linux/clock_settime.c
@@ -19,11 +19,9 @@ 
 #include <sysdep.h>
 #include <time.h>
 
-#include "kernel-posix-cpu-timers.h"
-
 /* Set CLOCK to value TP.  */
 int
-__clock_settime (clockid_t clock_id, const struct timespec *tp)
+__clock_settime64 (clockid_t clock_id, const struct __timespec64 *tp)
 {
   /* Make sure the time cvalue is OK.  */
   if (tp->tv_nsec < 0 || tp->tv_nsec >= 1000000000)
@@ -32,6 +30,52 @@  __clock_settime (clockid_t clock_id, const struct timespec *tp)
       return -1;
     }
 
+#if defined (__TIMESIZE) && __TIMESIZE != 64
+# ifdef __NR_clock_settime64
+  /* For 32 bit systems with no Y2038 support the *tp may have tv_pad
+     with some random values as *tp from __clock_settime is converted
+     to automatically allocated struct __timespec64 (ts64).
+
+     For 32 bit systems being Y2038 safe the tv_pad may be not zero,
+     as glibc exported struct timespec has 64 bit tv_sec, 32 bit
+     tv_nsec (to be still POSIX compliant -> long tv_nsec ) and 32
+     bits of unnamed padding.
+     If user program allocates the struct timespec automatically, the
+     padding may have random value and as being directly passed to
+     *tp needs to be cleared.  */
+  timespec64_clear_padding (tp);
+  int ret = INLINE_SYSCALL_CALL (clock_settime64, clock_id, tp);
+#  ifdef __ASSUME_64BIT_TIME
+  return ret;
+#  else
+  if (ret == 0 || errno != ENOSYS)
+    /* Preserve non-error/non-ENOSYS return values.  */
+    return ret;
+#  endif
+# endif
+  /* Fall back to syscall supporting 32bit struct timespec.  */
+  struct timespec ts32;
+  valid_timespec64_to_timespec (tp, &ts32);
+  return INLINE_SYSCALL_CALL (clock_settime, clock_id, &ts32);
+#else
   return INLINE_SYSCALL_CALL (clock_settime, clock_id, tp);
+#endif
 }
 weak_alias (__clock_settime, clock_settime)
+
+#if __TIMESIZE != 64
+int
+__clock_settime (clockid_t clock_id, const struct timespec *tp)
+{
+  struct __timespec64 ts64;
+
+  if (! in_time_t_range (tp->tv_sec))
+    {
+      __set_errno (EOVERFLOW);
+      return -1;
+    }
+
+  valid_timespec_to_timespec64 (tp, &ts64);
+  return __clock_settime64 (clock_id, &ts64);
+}
+#endif