diff mbox

[v3,01/17] y2038: asm-generic: Extend sysvipc data structures

Message ID 20180419143737.606138-2-arnd@arndb.de
State New
Headers show

Commit Message

Arnd Bergmann April 19, 2018, 2:37 p.m. UTC
Most architectures now use the asm-generic copy of the sysvipc data
structures (msqid64_ds, semid64_ds, shmid64_ds), which use 32-bit
__kernel_time_t on 32-bit architectures but have padding behind them to
allow extending the type to 64-bit.

Unfortunately, that fails on all big-endian architectures, which have the
padding on the wrong side. As so many of them get it wrong, we decided to
not bother even trying to fix it up when we introduced the asm-generic
copy. Instead we always use the padding word now to provide the upper
32 bits of the seconds value, regardless of the endianess.

A libc implementation on a typical big-endian system can deal with
this by providing its own copy of the structure definition to user
space, and swapping the two 32-bit words before returning from the
semctl/shmctl/msgctl system calls.

ARM64 and s/390 are architectures that use these generic headers and
also provide support for compat mode on 64-bit kernels, so we adapt
their copies here as well.

Signed-off-by: Arnd Bergmann <arnd@arndb.de>
---
 include/uapi/asm-generic/msgbuf.h | 17 ++++++++---------
 include/uapi/asm-generic/sembuf.h | 26 ++++++++++++++++----------
 include/uapi/asm-generic/shmbuf.h | 17 ++++++++---------
 3 files changed, 32 insertions(+), 28 deletions(-)

Comments

Eric W. Biederman April 19, 2018, 2:59 p.m. UTC | #1
Arnd Bergmann <arnd@arndb.de> writes:

> Most architectures now use the asm-generic copy of the sysvipc data
> structures (msqid64_ds, semid64_ds, shmid64_ds), which use 32-bit
> __kernel_time_t on 32-bit architectures but have padding behind them to
> allow extending the type to 64-bit.
>
> Unfortunately, that fails on all big-endian architectures, which have the
> padding on the wrong side. As so many of them get it wrong, we decided to
> not bother even trying to fix it up when we introduced the asm-generic
> copy. Instead we always use the padding word now to provide the upper
> 32 bits of the seconds value, regardless of the endianess.
>
> A libc implementation on a typical big-endian system can deal with
> this by providing its own copy of the structure definition to user
> space, and swapping the two 32-bit words before returning from the
> semctl/shmctl/msgctl system calls.
>
> ARM64 and s/390 are architectures that use these generic headers and
> also provide support for compat mode on 64-bit kernels, so we adapt
> their copies here as well.
>
> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
> ---
>  include/uapi/asm-generic/msgbuf.h | 17 ++++++++---------
>  include/uapi/asm-generic/sembuf.h | 26 ++++++++++++++++----------
>  include/uapi/asm-generic/shmbuf.h | 17 ++++++++---------
>  3 files changed, 32 insertions(+), 28 deletions(-)
>
> diff --git a/include/uapi/asm-generic/msgbuf.h b/include/uapi/asm-generic/msgbuf.h
> index fb306ebdb36f..d2169cae93b8 100644
> --- a/include/uapi/asm-generic/msgbuf.h
> +++ b/include/uapi/asm-generic/msgbuf.h
> @@ -18,23 +18,22 @@
>   * On big-endian systems, the padding is in the wrong place.
>   *
>   * Pad space is left for:
> - * - 64-bit time_t to solve y2038 problem
>   * - 2 miscellaneous 32-bit values
>   */
>  
>  struct msqid64_ds {
>  	struct ipc64_perm msg_perm;
> +#if __BITS_PER_LONG == 64
>  	__kernel_time_t msg_stime;	/* last msgsnd time */
> -#if __BITS_PER_LONG != 64
> -	unsigned long	__unused1;
> -#endif
>  	__kernel_time_t msg_rtime;	/* last msgrcv time */
> -#if __BITS_PER_LONG != 64
> -	unsigned long	__unused2;
> -#endif
>  	__kernel_time_t msg_ctime;	/* last change time */
> -#if __BITS_PER_LONG != 64
> -	unsigned long	__unused3;
> +#else
> +	unsigned long	msg_stime;	/* last msgsnd time */
> +	unsigned long	msg_stime_high;
> +	unsigned long	msg_rtime;	/* last msgrcv time */
> +	unsigned long	msg_rtime_high;
> +	unsigned long	msg_ctime;	/* last change time */
> +	unsigned long	msg_ctime_high;
>  #endif

I suspect you want to use __kernel_ulong_t here instead of a raw
unsigned long.  If nothing else it seems inconsistent to use typedefs
in one half of the structure and no typedefs in the other half.

>  	__kernel_ulong_t msg_cbytes;	/* current number of bytes on queue */
>  	__kernel_ulong_t msg_qnum;	/* number of messages in queue */
> diff --git a/include/uapi/asm-generic/sembuf.h b/include/uapi/asm-generic/sembuf.h
> index cbf9cfe977d6..0bae010f1b64 100644
> --- a/include/uapi/asm-generic/sembuf.h
> +++ b/include/uapi/asm-generic/sembuf.h
> @@ -13,23 +13,29 @@
>   * everyone just ended up making identical copies without specific
>   * optimizations, so we may just as well all use the same one.
>   *
> - * 64 bit architectures typically define a 64 bit __kernel_time_t,
> + * 64 bit architectures use a 64-bit __kernel_time_t here, while
> + * 32 bit architectures have a pair of unsigned long values.
>   * so they do not need the first two padding words.
> - * On big-endian systems, the padding is in the wrong place.
>   *
> - * Pad space is left for:
> - * - 64-bit time_t to solve y2038 problem
> - * - 2 miscellaneous 32-bit values
> + * On big-endian systems, the padding is in the wrong place for
> + * historic reasons, so user space has to reconstruct a time_t
> + * value using
> + *
> + * user_semid_ds.sem_otime = kernel_semid64_ds.sem_otime +
> + *		((long long)kernel_semid64_ds.sem_otime_high << 32)
> + *
> + * Pad space is left for 2 miscellaneous 32-bit values
>   */
>  struct semid64_ds {
>  	struct ipc64_perm sem_perm;	/* permissions .. see ipc.h */
> +#if __BITS_PER_LONG == 64
>  	__kernel_time_t	sem_otime;	/* last semop time */
> -#if __BITS_PER_LONG != 64
> -	unsigned long	__unused1;
> -#endif
>  	__kernel_time_t	sem_ctime;	/* last change time */
> -#if __BITS_PER_LONG != 64
> -	unsigned long	__unused2;
> +#else
> +	unsigned long	sem_otime;	/* last semop time */
> +	unsigned long	sem_otime_high;
> +	unsigned long	sem_ctime;	/* last change time */
> +	unsigned long	sem_ctime_high;
>  #endif
>  	unsigned long	sem_nsems;	/* no. of semaphores in array */
>  	unsigned long	__unused3;
> diff --git a/include/uapi/asm-generic/shmbuf.h b/include/uapi/asm-generic/shmbuf.h
> index 2b6c3bb97f97..602f1b5b462b 100644
> --- a/include/uapi/asm-generic/shmbuf.h
> +++ b/include/uapi/asm-generic/shmbuf.h
> @@ -19,24 +19,23 @@
>   *
>   *
>   * Pad space is left for:
> - * - 64-bit time_t to solve y2038 problem
>   * - 2 miscellaneous 32-bit values
>   */
>  
>  struct shmid64_ds {
>  	struct ipc64_perm	shm_perm;	/* operation perms */
>  	size_t			shm_segsz;	/* size of segment (bytes) */
> +#if __BITS_PER_LONG == 64
>  	__kernel_time_t		shm_atime;	/* last attach time */
> -#if __BITS_PER_LONG != 64
> -	unsigned long		__unused1;
> -#endif
>  	__kernel_time_t		shm_dtime;	/* last detach time */
> -#if __BITS_PER_LONG != 64
> -	unsigned long		__unused2;
> -#endif
>  	__kernel_time_t		shm_ctime;	/* last change time */
> -#if __BITS_PER_LONG != 64
> -	unsigned long		__unused3;
> +#else
> +	unsigned long		shm_atime;	/* last attach time */
> +	unsigned long		shm_atime_high;
> +	unsigned long		shm_dtime;	/* last detach time */
> +	unsigned long		shm_dtime_high;
> +	unsigned long		shm_ctime;	/* last change time */
> +	unsigned long		shm_ctime_high;
>  #endif
>  	__kernel_pid_t		shm_cpid;	/* pid of creator */
>  	__kernel_pid_t		shm_lpid;	/* pid of last operator */
Arnd Bergmann April 19, 2018, 3:20 p.m. UTC | #2
On Thu, Apr 19, 2018 at 4:59 PM, Eric W. Biederman
<ebiederm@xmission.com> wrote:
> Arnd Bergmann <arnd@arndb.de> writes:
>>
>>  struct msqid64_ds {
>>       struct ipc64_perm msg_perm;
>> +#if __BITS_PER_LONG == 64
>>       __kernel_time_t msg_stime;      /* last msgsnd time */
>> -#if __BITS_PER_LONG != 64
>> -     unsigned long   __unused1;
>> -#endif
>>       __kernel_time_t msg_rtime;      /* last msgrcv time */
>> -#if __BITS_PER_LONG != 64
>> -     unsigned long   __unused2;
>> -#endif
>>       __kernel_time_t msg_ctime;      /* last change time */
>> -#if __BITS_PER_LONG != 64
>> -     unsigned long   __unused3;
>> +#else
>> +     unsigned long   msg_stime;      /* last msgsnd time */
>> +     unsigned long   msg_stime_high;
>> +     unsigned long   msg_rtime;      /* last msgrcv time */
>> +     unsigned long   msg_rtime_high;
>> +     unsigned long   msg_ctime;      /* last change time */
>> +     unsigned long   msg_ctime_high;
>>  #endif
>
> I suspect you want to use __kernel_ulong_t here instead of a raw
> unsigned long.  If nothing else it seems inconsistent to use typedefs
> in one half of the structure and no typedefs in the other half.

Good catch, there is definitely something wrong here, but I think using
__kernel_ulong_t for all members would also be wrong, as that
still changes the layout on x32, which effectively is

struct msqid64_ds {
     ipc64_perm msg_perm;
     u64 msg_stime;
     u32 __unused1;
     /* 32 bit implict padding */
     u64 msg_rtime;
     u32 __unused2;
     /* 32 bit implict padding */
     u64 msg_ctime;
     u32 __unused3;
     /* 32 bit implict padding */
     __kernel_pid_t          shm_cpid;       /* pid of creator */
     __kernel_pid_t          shm_lpid;       /* pid of last operator */
     ....
};

The choices here would be to either use a mix of
__kernel_ulong_t and unsigned long, or taking the x32
version back into arch/x86/include/uapi/asm/ so the
generic version at least makes some sense.

I can't use __kernel_time_t for the lower half on 32-bit
since it really should be unsigned.

        Arnd
Zack Weinberg April 19, 2018, 3:30 p.m. UTC | #3
On Thu, Apr 19, 2018 at 10:37 AM, Arnd Bergmann <arnd@arndb.de> wrote:
> Most architectures now use the asm-generic copy of the sysvipc data
> structures (msqid64_ds, semid64_ds, shmid64_ds), which use 32-bit
> __kernel_time_t on 32-bit architectures but have padding behind them to
> allow extending the type to 64-bit.
>
> Unfortunately, that fails on all big-endian architectures, which have the
> padding on the wrong side. As so many of them get it wrong, we decided to
> not bother even trying to fix it up when we introduced the asm-generic
> copy. Instead we always use the padding word now to provide the upper
> 32 bits of the seconds value, regardless of the endianess.
>
> A libc implementation on a typical big-endian system can deal with
> this by providing its own copy of the structure definition to user
> space, and swapping the two 32-bit words before returning from the
> semctl/shmctl/msgctl system calls.

This seems generally like a sound approach, but I need to ask whether
any of the structures involved can ever appear in a sendmsg() control
message (that is, in the data pointed to by msg_control), or an
AF_NETLINK message, or any other situation where the kernel
communicates a structured message of arbitrary size to user space or
vice versa.  libc can't munge those messages, because new message
types can be added faster than libc can keep up with them, and because
I/O primitives like sendmsg() generally aren't allowed to allocate
arbitrarily-large scratch buffers.

zw
Arnd Bergmann April 19, 2018, 3:51 p.m. UTC | #4
On Thu, Apr 19, 2018 at 5:30 PM, Zack Weinberg <zackw@panix.com> wrote:
> On Thu, Apr 19, 2018 at 10:37 AM, Arnd Bergmann <arnd@arndb.de> wrote:
>> Most architectures now use the asm-generic copy of the sysvipc data
>> structures (msqid64_ds, semid64_ds, shmid64_ds), which use 32-bit
>> __kernel_time_t on 32-bit architectures but have padding behind them to
>> allow extending the type to 64-bit.
>>
>> Unfortunately, that fails on all big-endian architectures, which have the
>> padding on the wrong side. As so many of them get it wrong, we decided to
>> not bother even trying to fix it up when we introduced the asm-generic
>> copy. Instead we always use the padding word now to provide the upper
>> 32 bits of the seconds value, regardless of the endianess.
>>
>> A libc implementation on a typical big-endian system can deal with
>> this by providing its own copy of the structure definition to user
>> space, and swapping the two 32-bit words before returning from the
>> semctl/shmctl/msgctl system calls.
>
> This seems generally like a sound approach, but I need to ask whether
> any of the structures involved can ever appear in a sendmsg() control
> message (that is, in the data pointed to by msg_control), or an
> AF_NETLINK message, or any other situation where the kernel
> communicates a structured message of arbitrary size to user space or
> vice versa.  libc can't munge those messages, because new message
> types can be added faster than libc can keep up with them, and because
> I/O primitives like sendmsg() generally aren't allowed to allocate
> arbitrarily-large scratch buffers.

I'm fairly sure that the sysvipc data structures are entirely distinct from
the structures that get passed over sockets, so the question of socket
data is unrelated to this series and will be addressed in a separate
series.

To give some background on what needs to be done for sockets, the
only incompatibility I'm aware of are socket timestamps that
get enabled with SO_TIMESTAMP, SO_TIMESTAMPNS or
SO_TIMESTAMPING and get passed from kernel to user space
as SCM_TIMESTAMP/SCM_TIMESTAMPNS/SCM_TIMESTAMPING
cmsg data.

We already have code for handling 32-bit compat applications
on 64-bit kernels, but that cannot work for 32-bit applications
if the kernel has no idea whether the application uses 32-bit or
64-bit time_t, and we don't have a function like in_compat_syscall()
that we can use to find that out.

Our plan here is to change asm/socket.h to have three additional
timestamp flags that correspond to the existing SO_TIMESTAMP*
flags but signify that user space expects the new structure layout
(which is compatible with the existing layout on 64-bit kernels).
For each flag, the kernel then defines a wrapper that (on 32-bit
user space) looks like

#define SO_TIMESTAMP (sizeof(time_t) > sizeof(__kernel_long_t) ? \
             SO_TIMESTAMP_TIME64 : SO_TIMESTAMP_OLD)

Any application asking for SO_TIMESTAMP_OLD will get the
traditional behavior, while applications that are built with a
64-bit time_t will pass SO_TIMESTAMP_TIME64 into
setsockopts, causing the kernel to use the new behavior.
In 64-bit tasks, we probably want to define both to have existing
behavior even though one would never see the new macro.

         Arnd
Arnd Bergmann April 19, 2018, 9:24 p.m. UTC | #5
On Thu, Apr 19, 2018 at 5:20 PM, Arnd Bergmann <arnd@arndb.de> wrote:
> On Thu, Apr 19, 2018 at 4:59 PM, Eric W. Biederman <ebiederm@xmission.com> wrote:
>> I suspect you want to use __kernel_ulong_t here instead of a raw
>> unsigned long.  If nothing else it seems inconsistent to use typedefs
>> in one half of the structure and no typedefs in the other half.
>
> Good catch, there is definitely something wrong here, but I think using
> __kernel_ulong_t for all members would also be wrong, as that
> still changes the layout on x32, which effectively is
>
> struct msqid64_ds {
>      ipc64_perm msg_perm;
>      u64 msg_stime;
>      u32 __unused1;
>      /* 32 bit implict padding */
>      u64 msg_rtime;
>      u32 __unused2;
>      /* 32 bit implict padding */
>      u64 msg_ctime;
>      u32 __unused3;
>      /* 32 bit implict padding */
>      __kernel_pid_t          shm_cpid;       /* pid of creator */
>      __kernel_pid_t          shm_lpid;       /* pid of last operator */
>      ....
> };
>
> The choices here would be to either use a mix of
> __kernel_ulong_t and unsigned long, or taking the x32
> version back into arch/x86/include/uapi/asm/ so the
> generic version at least makes some sense.
>
> I can't use __kernel_time_t for the lower half on 32-bit
> since it really should be unsigned.

After thinking about it some more, I conclude that the structure is simply
incorrect on x32: The __kernel_ulong_t usage was introduced in 2013
in commit b9cd5ca22d67 ("uapi: Use __kernel_ulong_t in struct
msqid64_ds") and apparently was correct initially as __BITS_PER_LONG
evaluated to 64, but it broke with commit f4b4aae18288 ("x86/headers/uapi:
Fix __BITS_PER_LONG value for x32 builds") that changed the value
of __BITS_PER_LONG and introduced the extra padding in 2015.

The same change apparently also broke a lot of other definitions, e.g.

$ echo "#include <linux/types.h>" | gcc -mx32 -E -xc - | grep -A3
__kernel_size_t
typedef unsigned int __kernel_size_t;
typedef int __kernel_ssize_t;
typedef int __kernel_ptrdiff_t;

Those used to be defined as 'unsigned long long' and 'long long'
respectively, so now all kernel interfaces using those on x32
became incompatible!

       Arnd
Eric W. Biederman April 19, 2018, 10:12 p.m. UTC | #6
Arnd Bergmann <arnd@arndb.de> writes:

> On Thu, Apr 19, 2018 at 5:20 PM, Arnd Bergmann <arnd@arndb.de> wrote:
>> On Thu, Apr 19, 2018 at 4:59 PM, Eric W. Biederman <ebiederm@xmission.com> wrote:
>>> I suspect you want to use __kernel_ulong_t here instead of a raw
>>> unsigned long.  If nothing else it seems inconsistent to use typedefs
>>> in one half of the structure and no typedefs in the other half.
>>
>> Good catch, there is definitely something wrong here, but I think using
>> __kernel_ulong_t for all members would also be wrong, as that
>> still changes the layout on x32, which effectively is
>>
>> struct msqid64_ds {
>>      ipc64_perm msg_perm;
>>      u64 msg_stime;
>>      u32 __unused1;
>>      /* 32 bit implict padding */
>>      u64 msg_rtime;
>>      u32 __unused2;
>>      /* 32 bit implict padding */
>>      u64 msg_ctime;
>>      u32 __unused3;
>>      /* 32 bit implict padding */
>>      __kernel_pid_t          shm_cpid;       /* pid of creator */
>>      __kernel_pid_t          shm_lpid;       /* pid of last operator */
>>      ....
>> };
>>
>> The choices here would be to either use a mix of
>> __kernel_ulong_t and unsigned long, or taking the x32
>> version back into arch/x86/include/uapi/asm/ so the
>> generic version at least makes some sense.
>>
>> I can't use __kernel_time_t for the lower half on 32-bit
>> since it really should be unsigned.
>
> After thinking about it some more, I conclude that the structure is simply
> incorrect on x32: The __kernel_ulong_t usage was introduced in 2013
> in commit b9cd5ca22d67 ("uapi: Use __kernel_ulong_t in struct
> msqid64_ds") and apparently was correct initially as __BITS_PER_LONG
> evaluated to 64, but it broke with commit f4b4aae18288 ("x86/headers/uapi:
> Fix __BITS_PER_LONG value for x32 builds") that changed the value
> of __BITS_PER_LONG and introduced the extra padding in 2015.
>
> The same change apparently also broke a lot of other definitions, e.g.
>
> $ echo "#include <linux/types.h>" | gcc -mx32 -E -xc - | grep -A3
> __kernel_size_t
> typedef unsigned int __kernel_size_t;
> typedef int __kernel_ssize_t;
> typedef int __kernel_ptrdiff_t;
>
> Those used to be defined as 'unsigned long long' and 'long long'
> respectively, so now all kernel interfaces using those on x32
> became incompatible!

That seems like a real mess.

Is this just for the uapi header as seen by userspace?  I expect we are
using the a normal kernel interface with 64bit longs and 64bit pointers
when we build the kernel.

If this is just a header as seen from userspace mess it seems
unfortunate but fixable.

Eric
Arnd Bergmann April 20, 2018, 8:54 a.m. UTC | #7
On Fri, Apr 20, 2018 at 12:12 AM, Eric W. Biederman
<ebiederm@xmission.com> wrote:
> Arnd Bergmann <arnd@arndb.de> writes:
>
>> On Thu, Apr 19, 2018 at 5:20 PM, Arnd Bergmann <arnd@arndb.de> wrote:
>>> On Thu, Apr 19, 2018 at 4:59 PM, Eric W. Biederman <ebiederm@xmission.com> wrote:
>>>> I suspect you want to use __kernel_ulong_t here instead of a raw
>>>> unsigned long.  If nothing else it seems inconsistent to use typedefs
>>>> in one half of the structure and no typedefs in the other half.
>>>
>>> Good catch, there is definitely something wrong here, but I think using
>>> __kernel_ulong_t for all members would also be wrong, as that
>>> still changes the layout on x32, which effectively is
>>>
>>> struct msqid64_ds {
>>>      ipc64_perm msg_perm;
>>>      u64 msg_stime;
>>>      u32 __unused1;
>>>      /* 32 bit implict padding */
>>>      u64 msg_rtime;
>>>      u32 __unused2;
>>>      /* 32 bit implict padding */
>>>      u64 msg_ctime;
>>>      u32 __unused3;
>>>      /* 32 bit implict padding */
>>>      __kernel_pid_t          shm_cpid;       /* pid of creator */
>>>      __kernel_pid_t          shm_lpid;       /* pid of last operator */
>>>      ....
>>> };
>>>
>>> The choices here would be to either use a mix of
>>> __kernel_ulong_t and unsigned long, or taking the x32
>>> version back into arch/x86/include/uapi/asm/ so the
>>> generic version at least makes some sense.
>>>
>>> I can't use __kernel_time_t for the lower half on 32-bit
>>> since it really should be unsigned.
>>
>> After thinking about it some more, I conclude that the structure is simply
>> incorrect on x32: The __kernel_ulong_t usage was introduced in 2013
>> in commit b9cd5ca22d67 ("uapi: Use __kernel_ulong_t in struct
>> msqid64_ds") and apparently was correct initially as __BITS_PER_LONG
>> evaluated to 64, but it broke with commit f4b4aae18288 ("x86/headers/uapi:
>> Fix __BITS_PER_LONG value for x32 builds") that changed the value
>> of __BITS_PER_LONG and introduced the extra padding in 2015.
>>
>> The same change apparently also broke a lot of other definitions, e.g.
>>
>> $ echo "#include <linux/types.h>" | gcc -mx32 -E -xc - | grep -A3
>> __kernel_size_t
>> typedef unsigned int __kernel_size_t;
>> typedef int __kernel_ssize_t;
>> typedef int __kernel_ptrdiff_t;
>>
>> Those used to be defined as 'unsigned long long' and 'long long'
>> respectively, so now all kernel interfaces using those on x32
>> became incompatible!
>
> Is this just for the uapi header as seen by userspace?  I expect we are
> using the a normal kernel interface with 64bit longs and 64bit pointers
> when we build the kernel.

Yes, that patch shouldn't have changed anything in the kernel, which
continues to be built with __BITS_PER_LONG=64. I haven't
checked the vdso, which is the only bit of the kernel that gets built
with -mx32, but I assume it's fine as well.

> If this is just a header as seen from userspace mess it seems
> unfortunate but fixable.

Right. I'll fix the IPC stuff for this series to make it work with
any value of __BITS_PER_LONG on x32, but I don't plan to
do anything about the rest of x32. The patch that caused the
problem was intended as a bugfix, so we can't just revert it
without first understanding how to properly fix the original bug,
and which other interfaces have now come to rely on
__BITS_PER_LONG=32 for x32.

Adding a few other folks that have been involved in the x32
kernel support or the Debian port in the past. Maybe one of
them is motivated to figure out how to fix this properly.

       Arnd
diff mbox

Patch

diff --git a/include/uapi/asm-generic/msgbuf.h b/include/uapi/asm-generic/msgbuf.h
index fb306ebdb36f..d2169cae93b8 100644
--- a/include/uapi/asm-generic/msgbuf.h
+++ b/include/uapi/asm-generic/msgbuf.h
@@ -18,23 +18,22 @@ 
  * On big-endian systems, the padding is in the wrong place.
  *
  * Pad space is left for:
- * - 64-bit time_t to solve y2038 problem
  * - 2 miscellaneous 32-bit values
  */
 
 struct msqid64_ds {
 	struct ipc64_perm msg_perm;
+#if __BITS_PER_LONG == 64
 	__kernel_time_t msg_stime;	/* last msgsnd time */
-#if __BITS_PER_LONG != 64
-	unsigned long	__unused1;
-#endif
 	__kernel_time_t msg_rtime;	/* last msgrcv time */
-#if __BITS_PER_LONG != 64
-	unsigned long	__unused2;
-#endif
 	__kernel_time_t msg_ctime;	/* last change time */
-#if __BITS_PER_LONG != 64
-	unsigned long	__unused3;
+#else
+	unsigned long	msg_stime;	/* last msgsnd time */
+	unsigned long	msg_stime_high;
+	unsigned long	msg_rtime;	/* last msgrcv time */
+	unsigned long	msg_rtime_high;
+	unsigned long	msg_ctime;	/* last change time */
+	unsigned long	msg_ctime_high;
 #endif
 	__kernel_ulong_t msg_cbytes;	/* current number of bytes on queue */
 	__kernel_ulong_t msg_qnum;	/* number of messages in queue */
diff --git a/include/uapi/asm-generic/sembuf.h b/include/uapi/asm-generic/sembuf.h
index cbf9cfe977d6..0bae010f1b64 100644
--- a/include/uapi/asm-generic/sembuf.h
+++ b/include/uapi/asm-generic/sembuf.h
@@ -13,23 +13,29 @@ 
  * everyone just ended up making identical copies without specific
  * optimizations, so we may just as well all use the same one.
  *
- * 64 bit architectures typically define a 64 bit __kernel_time_t,
+ * 64 bit architectures use a 64-bit __kernel_time_t here, while
+ * 32 bit architectures have a pair of unsigned long values.
  * so they do not need the first two padding words.
- * On big-endian systems, the padding is in the wrong place.
  *
- * Pad space is left for:
- * - 64-bit time_t to solve y2038 problem
- * - 2 miscellaneous 32-bit values
+ * On big-endian systems, the padding is in the wrong place for
+ * historic reasons, so user space has to reconstruct a time_t
+ * value using
+ *
+ * user_semid_ds.sem_otime = kernel_semid64_ds.sem_otime +
+ *		((long long)kernel_semid64_ds.sem_otime_high << 32)
+ *
+ * Pad space is left for 2 miscellaneous 32-bit values
  */
 struct semid64_ds {
 	struct ipc64_perm sem_perm;	/* permissions .. see ipc.h */
+#if __BITS_PER_LONG == 64
 	__kernel_time_t	sem_otime;	/* last semop time */
-#if __BITS_PER_LONG != 64
-	unsigned long	__unused1;
-#endif
 	__kernel_time_t	sem_ctime;	/* last change time */
-#if __BITS_PER_LONG != 64
-	unsigned long	__unused2;
+#else
+	unsigned long	sem_otime;	/* last semop time */
+	unsigned long	sem_otime_high;
+	unsigned long	sem_ctime;	/* last change time */
+	unsigned long	sem_ctime_high;
 #endif
 	unsigned long	sem_nsems;	/* no. of semaphores in array */
 	unsigned long	__unused3;
diff --git a/include/uapi/asm-generic/shmbuf.h b/include/uapi/asm-generic/shmbuf.h
index 2b6c3bb97f97..602f1b5b462b 100644
--- a/include/uapi/asm-generic/shmbuf.h
+++ b/include/uapi/asm-generic/shmbuf.h
@@ -19,24 +19,23 @@ 
  *
  *
  * Pad space is left for:
- * - 64-bit time_t to solve y2038 problem
  * - 2 miscellaneous 32-bit values
  */
 
 struct shmid64_ds {
 	struct ipc64_perm	shm_perm;	/* operation perms */
 	size_t			shm_segsz;	/* size of segment (bytes) */
+#if __BITS_PER_LONG == 64
 	__kernel_time_t		shm_atime;	/* last attach time */
-#if __BITS_PER_LONG != 64
-	unsigned long		__unused1;
-#endif
 	__kernel_time_t		shm_dtime;	/* last detach time */
-#if __BITS_PER_LONG != 64
-	unsigned long		__unused2;
-#endif
 	__kernel_time_t		shm_ctime;	/* last change time */
-#if __BITS_PER_LONG != 64
-	unsigned long		__unused3;
+#else
+	unsigned long		shm_atime;	/* last attach time */
+	unsigned long		shm_atime_high;
+	unsigned long		shm_dtime;	/* last detach time */
+	unsigned long		shm_dtime_high;
+	unsigned long		shm_ctime;	/* last change time */
+	unsigned long		shm_ctime_high;
 #endif
 	__kernel_pid_t		shm_cpid;	/* pid of creator */
 	__kernel_pid_t		shm_lpid;	/* pid of last operator */