include/uapi/linux/time_types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
'struct __kernel_old_timespec' represents the 'native' time ABI of the
kernel. On 32-bit systems it uses 32-bit fields and on 64-bit systems
it uses 64-bit fields.
However the x86 x32 ABI uses the 64-bit time ABI natively.
This is correctly handled for the 'tv_sec' fields, through the typedefs
of '__kernel_old_time_t' -> '__kernel_long_t' -> 'long long'. The same
treatment was missed for 'tv_nsec'.
In practice this might not make much of a difference as the value of
'tv_nsec' will always fit into 32 bits and the missing bits fall
into the padding of the structure.
When introspecting the structure however, a difference can be observed.
Switch to 64-bit tv_nsec on x32.
No other architectures or ABIs are affected.
Fixes: 94c467ddb273 ("y2038: add __kernel_old_timespec and __kernel_old_time_t")
Signed-off-by: Thomas Weißschuh <thomas.weissschuh@linutronix.de>
---
Arnd, can you take this?
I'm not entirely sure if this warrants a stable backport.
---
include/uapi/linux/time_types.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/include/uapi/linux/time_types.h b/include/uapi/linux/time_types.h
index bcc0002115d3..03a0d8aaadca 100644
--- a/include/uapi/linux/time_types.h
+++ b/include/uapi/linux/time_types.h
@@ -30,7 +30,7 @@ struct __kernel_old_timeval {
struct __kernel_old_timespec {
__kernel_old_time_t tv_sec; /* seconds */
- long tv_nsec; /* nanoseconds */
+ __kernel_long_t tv_nsec; /* nanoseconds */
};
struct __kernel_old_itimerval {
---
base-commit: 6de23f81a5e08be8fbf5e8d7e9febc72a5b5f27f
change-id: 20260224-timespec-x32-0f9c15531320
Best regards,
--
Thomas Weißschuh <thomas.weissschuh@linutronix.de>
On Fri, Feb 27, 2026, at 08:18, Thomas Weißschuh wrote:
> 'struct __kernel_old_timespec' represents the 'native' time ABI of the
> kernel. On 32-bit systems it uses 32-bit fields and on 64-bit systems
> it uses 64-bit fields.
> However the x86 x32 ABI uses the 64-bit time ABI natively.
> This is correctly handled for the 'tv_sec' fields, through the typedefs
> of '__kernel_old_time_t' -> '__kernel_long_t' -> 'long long'. The same
> treatment was missed for 'tv_nsec'.
Not sure about this patch, the choice of 'long' was clearly deliberate
here because that is what POSIX mandates for 'struct timespec', and what
glibc uses as well. Any access to an actual userspec timespec
is supposed to go through get_timespec64(), which handles the padding
like this
int get_timespec64(struct timespec64 *ts,
const struct __kernel_timespec __user *uts)
{
struct __kernel_timespec kts;
int ret;
ret = copy_from_user(&kts, uts, sizeof(kts));
if (ret)
return -EFAULT;
ts->tv_sec = kts.tv_sec;
/* Zero out the padding in compat mode */
if (in_compat_syscall())
kts.tv_nsec &= 0xFFFFFFFFUL;
/* In 32-bit mode, this drops the padding */
ts->tv_nsec = kts.tv_nsec;
return 0;
}
If there are any code paths that are missing this, they are likely
also broken for all other compat tasks in the time64 syscalls
> @@ -30,7 +30,7 @@ struct __kernel_old_timeval {
>
> struct __kernel_old_timespec {
> __kernel_old_time_t tv_sec; /* seconds */
> - long tv_nsec; /* nanoseconds */
> + __kernel_long_t tv_nsec; /* nanoseconds */
> };
I think the hidden padding is an actual problem here, but I
would address that with extra padding, similar to what glibc
has, e.g.
struct __kernel_old_timespec {
__kernel_old_time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
#if defined(__x86_64__) && defined(__ILP32__)
long :32; /* pad to alignment */
#endif
};
It may be better to duplicate the structure definition and keep
the x32 hack in arch/x86/uapi/asm/posix_types.h, the same way
we do for the sparc64 __kernel_old_timeval, which has a very
similar issue.
Arnd
On Fri, Feb 27, 2026 at 10:05:25AM +0100, Arnd Bergmann wrote:
> On Fri, Feb 27, 2026, at 08:18, Thomas Weißschuh wrote:
> > 'struct __kernel_old_timespec' represents the 'native' time ABI of the
> > kernel. On 32-bit systems it uses 32-bit fields and on 64-bit systems
> > it uses 64-bit fields.
> > However the x86 x32 ABI uses the 64-bit time ABI natively.
> > This is correctly handled for the 'tv_sec' fields, through the typedefs
> > of '__kernel_old_time_t' -> '__kernel_long_t' -> 'long long'. The same
> > treatment was missed for 'tv_nsec'.
>
> Not sure about this patch, the choice of 'long' was clearly deliberate
> here because that is what POSIX mandates for 'struct timespec', and what
> glibc uses as well. Any access to an actual userspec timespec
> is supposed to go through get_timespec64(), which handles the padding
> like this
Do we need to follow POSIX here? This is not a POSIX type.
POSIX also specifies 'tv_sec' to be 'time_t', which it isn't here.
Weirdly enough timespec(3type) says this:
tv_nsec is of an implementation-defined signed type capable of
holding the specified range. Under glibc, this is usually long,
and long long on X32. It can be safely down-cast to any concrete
32-bit integer type for processing.
Which does make sense, as x32 uses the time ABI from x86_64, and there
tv_nsec is a 64-bit value.
> int get_timespec64(struct timespec64 *ts,
> const struct __kernel_timespec __user *uts)
> {
> struct __kernel_timespec kts;
> int ret;
>
> ret = copy_from_user(&kts, uts, sizeof(kts));
> if (ret)
> return -EFAULT;
>
> ts->tv_sec = kts.tv_sec;
>
> /* Zero out the padding in compat mode */
> if (in_compat_syscall())
> kts.tv_nsec &= 0xFFFFFFFFUL;
>
> /* In 32-bit mode, this drops the padding */
> ts->tv_nsec = kts.tv_nsec;
>
> return 0;
> }
>
> If there are any code paths that are missing this, they are likely
> also broken for all other compat tasks in the time64 syscalls
Ack.
> > @@ -30,7 +30,7 @@ struct __kernel_old_timeval {
> >
> > struct __kernel_old_timespec {
> > __kernel_old_time_t tv_sec; /* seconds */
> > - long tv_nsec; /* nanoseconds */
> > + __kernel_long_t tv_nsec; /* nanoseconds */
> > };
>
> I think the hidden padding is an actual problem here, but I
> would address that with extra padding, similar to what glibc
> has, e.g.
>
> struct __kernel_old_timespec {
> __kernel_old_time_t tv_sec; /* seconds */
> long tv_nsec; /* nanoseconds */
> #if defined(__x86_64__) && defined(__ILP32__)
> long :32; /* pad to alignment */
> #endif
> };
This patch is a fallout of [0]. With it the type assertions become simpler.
The additional padding would not solve this "issue".
> It may be better to duplicate the structure definition and keep
> the x32 hack in arch/x86/uapi/asm/posix_types.h, the same way
> we do for the sparc64 __kernel_old_timeval, which has a very
> similar issue.
That would be an alternative, yes.
[0] https://lore.kernel.org/lkml/20251113-vdso-test-types-v2-3-0427eff70d08@linutronix.de/
Thomas
© 2016 - 2026 Red Hat, Inc.