diff mbox series

[v3,05/24] linux: Add fallback for 64-bit time_t SO_TIMESTAMP{NS}

Message ID 20210607203613.282543-6-adhemerval.zanella@linaro.org
State New
Headers show
Series Add 64 bit time support on legacy ABIs | expand

Commit Message

Adhemerval Zanella June 7, 2021, 8:35 p.m. UTC
The recvmsg handling is more complicated because it requires check the
returned kernel control message and make some convertions.  For
!__ASSUME_TIME64_SYSCALLS it converts the first 32-bit time SO_TIMESTAMP
or SO_TIMESTAMPNS and appends it to the control buffer if has extra
space or returns MSG_CTRUNC otherwise.  The 32-bit time field is kept
as-is.

Calls with __TIMESIZE=32 will see the converted 64-bit time control
messages as spurious control message of unknown type.  Calls with
__TIMESIZE=64 running on pre-time64 kernels will see the original
message as a spurious control ones of unknown typ while running on
kernel with native 64-bit time support will only see the time64 version
of the control message.

Checked on x86_64-linux-gnu and i686-linux-gnu (on 5.4 and on 4.15
kernel).

Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Tested-by: Carlos O'Donell <carlos@redhat.com>
---
 include/sys/socket.h                          |  5 +
 sysdeps/unix/sysv/linux/Makefile              |  2 +-
 sysdeps/unix/sysv/linux/Versions              |  1 +
 .../unix/sysv/linux/convert_scm_timestamps.c  | 96 +++++++++++++++++++
 sysdeps/unix/sysv/linux/getsockopt.c          | 12 +++
 .../sysv/linux/hppa/socket-constants-time64.h |  5 +
 .../sysv/linux/mips/socket-constants-time64.h |  5 +
 .../linux/powerpc/socket-constants-time64.h   |  5 +
 sysdeps/unix/sysv/linux/recvmsg.c             | 23 +++--
 sysdeps/unix/sysv/linux/setsockopt.c          | 12 +++
 .../unix/sysv/linux/socket-constants-time64.h |  5 +
 .../linux/sparc/socket-constants-time64.h     |  5 +
 12 files changed, 169 insertions(+), 7 deletions(-)
 create mode 100644 sysdeps/unix/sysv/linux/convert_scm_timestamps.c

Comments

Florian Weimer June 25, 2021, 3:20 p.m. UTC | #1
* Adhemerval Zanella via Libc-alpha:

> +/* It converts the first SO_TIMESTAMP or SO_TIMESTAMPNS with 32-bit time and
> +   appends it to the control buffer.  The 32-bit time field is kept as-is.
> +
> +   Calls with __TIMESIZE=32 will see the converted 64-bit time control
> +   messages as spurious control message of unknown type.
> +
> +   Calls with __TIMESIZE=64 running on pre-time64 kernels will see the
> +   original message as a spurious control ones of unknown typ while running
> +   on kernel with native 64-bit time support will only see the time64 version
> +   of the control message.  */
> +void
> +__convert_scm_timestamps (struct msghdr *msg, socklen_t msgsize)
> +{
> +  if (msg->msg_control == NULL || msg->msg_controllen == 0)
> +    return;
> +
> +  /* The returned control message format for SO_TIMESTAMP_NEW is a
> +     'struct __kernel_sock_timeval' while for SO_TIMESTAMPNS_NEW is a
> +     'struct __kernel_timespec'.  In either case it is two uint64_t
> +     members.  */
> +  uint64_t tvts[2];
> +
> +  struct cmsghdr *cmsg, *last = NULL;
> +  int type = 0;
> +
> +  for (cmsg = CMSG_FIRSTHDR (msg);
> +       cmsg != NULL;
> +       cmsg = CMSG_NXTHDR (msg, cmsg))
> +    {
> +      if (cmsg->cmsg_level != SOL_SOCKET)
> +	continue;
> +
> +      switch (cmsg->cmsg_type)
> +	{
> +	case COMPAT_SO_TIMESTAMP_OLD:
> +	  if (type != 0)
> +	    break;
> +	  type = COMPAT_SO_TIMESTAMP_NEW;
> +	  goto common;
> +
> +	case COMPAT_SO_TIMESTAMPNS_OLD:
> +	  type = COMPAT_SO_TIMESTAMPNS_NEW;
> +
> +	/* fallthrough  */
> +	common:
> +	  memcpy (tvts, CMSG_DATA (cmsg), sizeof (tvts));
> +	  break;
> +	}
> +
> +      last = cmsg;
> +    }
> +
> +  if (last == NULL || type == 0)
> +    return;
> +
> +  if (CMSG_SPACE (sizeof tvts) > msgsize - msg->msg_controllen)
> +    {
> +      msg->msg_flags |= MSG_CTRUNC;
> +      return;
> +    }
> +
> +  msg->msg_controllen += CMSG_SPACE (sizeof tvts);
> +  cmsg = CMSG_NXTHDR(msg, last);
> +  cmsg->cmsg_level = SOL_SOCKET;
> +  cmsg->cmsg_type = type;
> +  cmsg->cmsg_len = CMSG_LEN (sizeof tvts);
> +  memcpy (CMSG_DATA (cmsg), tvts, sizeof tvts);
> +}
> +libc_hidden_def (__convert_scm_timestamps)
> +#endif

The Ruby test suite crashes on this line:

  cmsg->cmsg_level = SOL_SOCKET;

See:

  ruby: FTBFS with test suite failure (glibc 2.34 related)
  <https://bugzilla.redhat.com/show_bug.cgi?id=1975144>

The disassembly suggests that GCC has detected some undefined behavior.

This looks like a related bug:

  __cmsg_nxthdr in cmsg_nxthdr.c (CMSG_NXTHDR) has undefined behavior when setting up ancillary data
  <https://sourceware.org/bugzilla/show_bug.cgi?id=13500>

I believe you cannot use CMSG_NXTHDR to append data in this way.

The other question is why this code is running at all.  Doing this
complex conversion for a 32-bit applications doing a 32-bit function
call on a kernel which supports 32-bit system calls does not make much
sense to me.

Thanks,
Florian
Adhemerval Zanella June 25, 2021, 6:11 p.m. UTC | #2
On 25/06/2021 12:20, Florian Weimer wrote:
> * Adhemerval Zanella via Libc-alpha:
> 
>> +/* It converts the first SO_TIMESTAMP or SO_TIMESTAMPNS with 32-bit time and
>> +   appends it to the control buffer.  The 32-bit time field is kept as-is.
>> +
>> +   Calls with __TIMESIZE=32 will see the converted 64-bit time control
>> +   messages as spurious control message of unknown type.
>> +
>> +   Calls with __TIMESIZE=64 running on pre-time64 kernels will see the
>> +   original message as a spurious control ones of unknown typ while running
>> +   on kernel with native 64-bit time support will only see the time64 version
>> +   of the control message.  */
>> +void
>> +__convert_scm_timestamps (struct msghdr *msg, socklen_t msgsize)
>> +{
>> +  if (msg->msg_control == NULL || msg->msg_controllen == 0)
>> +    return;
>> +
>> +  /* The returned control message format for SO_TIMESTAMP_NEW is a
>> +     'struct __kernel_sock_timeval' while for SO_TIMESTAMPNS_NEW is a
>> +     'struct __kernel_timespec'.  In either case it is two uint64_t
>> +     members.  */
>> +  uint64_t tvts[2];
>> +
>> +  struct cmsghdr *cmsg, *last = NULL;
>> +  int type = 0;
>> +
>> +  for (cmsg = CMSG_FIRSTHDR (msg);
>> +       cmsg != NULL;
>> +       cmsg = CMSG_NXTHDR (msg, cmsg))
>> +    {
>> +      if (cmsg->cmsg_level != SOL_SOCKET)
>> +	continue;
>> +
>> +      switch (cmsg->cmsg_type)
>> +	{
>> +	case COMPAT_SO_TIMESTAMP_OLD:
>> +	  if (type != 0)
>> +	    break;
>> +	  type = COMPAT_SO_TIMESTAMP_NEW;
>> +	  goto common;
>> +
>> +	case COMPAT_SO_TIMESTAMPNS_OLD:
>> +	  type = COMPAT_SO_TIMESTAMPNS_NEW;
>> +
>> +	/* fallthrough  */
>> +	common:
>> +	  memcpy (tvts, CMSG_DATA (cmsg), sizeof (tvts));
>> +	  break;
>> +	}
>> +
>> +      last = cmsg;
>> +    }
>> +
>> +  if (last == NULL || type == 0)
>> +    return;
>> +
>> +  if (CMSG_SPACE (sizeof tvts) > msgsize - msg->msg_controllen)
>> +    {
>> +      msg->msg_flags |= MSG_CTRUNC;
>> +      return;
>> +    }
>> +
>> +  msg->msg_controllen += CMSG_SPACE (sizeof tvts);
>> +  cmsg = CMSG_NXTHDR(msg, last);
>> +  cmsg->cmsg_level = SOL_SOCKET;
>> +  cmsg->cmsg_type = type;
>> +  cmsg->cmsg_len = CMSG_LEN (sizeof tvts);
>> +  memcpy (CMSG_DATA (cmsg), tvts, sizeof tvts);
>> +}
>> +libc_hidden_def (__convert_scm_timestamps)
>> +#endif
> 
> The Ruby test suite crashes on this line:
> 
>   cmsg->cmsg_level = SOL_SOCKET;
> 
> See:
> 
>   ruby: FTBFS with test suite failure (glibc 2.34 related)
>   <https://bugzilla.redhat.com/show_bug.cgi?id=1975144>

Do we have a more contained testcase? I am trying to trigger using the ruby
example it is kind hard to no make it use the system libraries.

I am trying to create a testcase with different cmsghdr sizes, but at least 
on i686 I can't really reproduce the issue (I am also running on 5.11 kernel).

> 
> The disassembly suggests that GCC has detected some undefined behavior.
> 
> This looks like a related bug:
> 
>   __cmsg_nxthdr in cmsg_nxthdr.c (CMSG_NXTHDR) has undefined behavior when setting up ancillary data
>   <https://sourceware.org/bugzilla/show_bug.cgi?id=13500>
> 
> I believe you cannot use CMSG_NXTHDR to append data in this way.

I think I am getting luck here because the example provided does pass,
even when tested against valgrind, asan, and ubsan (using gcc 10).

> 
> The other question is why this code is running at all.  Doing this
> complex conversion for a 32-bit applications doing a 32-bit function
> call on a kernel which supports 32-bit system calls does not make much
> sense to me.

Mainly because kernel does not provide a 64-bit recvmsg, different than
recvmmg.  So for 64-bit time_t calls we need to do the conversion,
although it won't help much if the caller does not provide a buffer large
enough.

I don't think we can improve it much by adding a 64-bit symbol: the
underlying syscall will be the same we don't prior hand which
SO_TIMESTAMP value were used to setup the timer (32-bit or 64-bit
one).

Revising the code I found one issue with __convert_scm_timestamps,
where the memcpy might indeed being accessing invalid memory:

diff --git a/sysdeps/unix/sysv/linux/convert_scm_timestamps.c b/sysdeps/unix/sysv/linux/convert_scm_timestamps.c
index d75a4618dd..2c61267fec 100644
--- a/sysdeps/unix/sysv/linux/convert_scm_timestamps.c
+++ b/sysdeps/unix/sysv/linux/convert_scm_timestamps.c
@@ -44,7 +44,8 @@ __convert_scm_timestamps (struct msghdr *msg, socklen_t msgsize)
      'struct __kernel_sock_timeval' while for SO_TIMESTAMPNS_NEW is a
      'struct __kernel_timespec'.  In either case it is two uint64_t
      members.  */
-  uint64_t tvts[2];
+  int64_t tvts[2];
+  int32_t tmp;
 
   struct cmsghdr *cmsg, *last = NULL;
   int type = 0;
@@ -69,7 +70,10 @@ __convert_scm_timestamps (struct msghdr *msg, socklen_t msgsize)
 
        /* fallthrough  */
        common:
-         memcpy (tvts, CMSG_DATA (cmsg), sizeof (tvts));
+         memcpy (&tmp, CMSG_DATA (cmsg), sizeof (tmp));
+         tvts[0] = tmp;
+         memcpy (&tmp, CMSG_DATA (cmsg) + sizeof (tmp), sizeof (tmp));
+         tvts[1] = tmp;
          break;
        }
Florian Weimer June 25, 2021, 7:16 p.m. UTC | #3
* Adhemerval Zanella:

>> See:
>> 
>>   ruby: FTBFS with test suite failure (glibc 2.34 related)
>>   <https://bugzilla.redhat.com/show_bug.cgi?id=1975144>
>
> Do we have a more contained testcase? I am trying to trigger using the ruby
> example it is kind hard to no make it use the system libraries.
>
> I am trying to create a testcase with different cmsghdr sizes, but at least 
> on i686 I can't really reproduce the issue (I am also running on 5.11
> kernel).

The ruby test case needs “set disable-randomization off” in GDB and even
then passes sporadically.  Without it, the test always succeeds for some
reason.

With the GCC 11 compiled binaries in Fedora, we fault in an isolated
error path (null pointer store followed by ud2 instruction).  I haven't
had time to look at the disassembly and GIMPLE dumps to see what is
going on.

>> The disassembly suggests that GCC has detected some undefined behavior.
>> 
>> This looks like a related bug:
>> 
>>   __cmsg_nxthdr in cmsg_nxthdr.c (CMSG_NXTHDR) has undefined behavior when setting up ancillary data
>>   <https://sourceware.org/bugzilla/show_bug.cgi?id=13500>
>> 
>> I believe you cannot use CMSG_NXTHDR to append data in this way.
>
> I think I am getting luck here because the example provided does pass,
> even when tested against valgrind, asan, and ubsan (using gcc 10).

Hmm.  I think it depends on previous buffer contents.

>> The other question is why this code is running at all.  Doing this
>> complex conversion for a 32-bit applications doing a 32-bit function
>> call on a kernel which supports 32-bit system calls does not make much
>> sense to me.
>
> Mainly because kernel does not provide a 64-bit recvmsg, different than
> recvmmg.  So for 64-bit time_t calls we need to do the conversion,
> although it won't help much if the caller does not provide a buffer large
> enough.

Yes, but the Fedora build does not use 64-bit time_t, so this conversion
is kind of pointless there.

I see that we didn't add a __recvmsg_time64 entrypoint (and similar
entrypoints for the other cases).  I think that's a mistake, we should
have those to future-proof things (similar for ioctl and fcntl—are there
any other multiplexers?).

> I don't think we can improve it much by adding a 64-bit symbol: the
> underlying syscall will be the same we don't prior hand which
> SO_TIMESTAMP value were used to setup the timer (32-bit or 64-bit
> one).

Yes, but old 32-bit binaries can avoid running the new code if we have
separate entrypoint.

> Revising the code I found one issue with __convert_scm_timestamps,
> where the memcpy might indeed being accessing invalid memory:
>
> diff --git a/sysdeps/unix/sysv/linux/convert_scm_timestamps.c b/sysdeps/unix/sysv/linux/convert_scm_timestamps.c
> index d75a4618dd..2c61267fec 100644
> --- a/sysdeps/unix/sysv/linux/convert_scm_timestamps.c
> +++ b/sysdeps/unix/sysv/linux/convert_scm_timestamps.c
> @@ -44,7 +44,8 @@ __convert_scm_timestamps (struct msghdr *msg, socklen_t msgsize)
>       'struct __kernel_sock_timeval' while for SO_TIMESTAMPNS_NEW is a
>       'struct __kernel_timespec'.  In either case it is two uint64_t
>       members.  */
> -  uint64_t tvts[2];
> +  int64_t tvts[2];
> +  int32_t tmp;
>  
>    struct cmsghdr *cmsg, *last = NULL;
>    int type = 0;
> @@ -69,7 +70,10 @@ __convert_scm_timestamps (struct msghdr *msg, socklen_t msgsize)
>  
>         /* fallthrough  */
>         common:
> -         memcpy (tvts, CMSG_DATA (cmsg), sizeof (tvts));
> +         memcpy (&tmp, CMSG_DATA (cmsg), sizeof (tmp));
> +         tvts[0] = tmp;
> +         memcpy (&tmp, CMSG_DATA (cmsg) + sizeof (tmp), sizeof (tmp));
> +         tvts[1] = tmp;
>           break;
>         }

Sorry, I can't quite wrap my head around this right now.

Thanks,
Florian
Adhemerval Zanella June 28, 2021, 1:36 p.m. UTC | #4
On 25/06/2021 16:16, Florian Weimer wrote:
> * Adhemerval Zanella:
> 
>>> See:
>>>
>>>   ruby: FTBFS with test suite failure (glibc 2.34 related)
>>>   <https://bugzilla.redhat.com/show_bug.cgi?id=1975144>
>>
>> Do we have a more contained testcase? I am trying to trigger using the ruby
>> example it is kind hard to no make it use the system libraries.
>>
>> I am trying to create a testcase with different cmsghdr sizes, but at least 
>> on i686 I can't really reproduce the issue (I am also running on 5.11
>> kernel).
> 
> The ruby test case needs “set disable-randomization off” in GDB and even
> then passes sporadically.  Without it, the test always succeeds for some
> reason.
> 
> With the GCC 11 compiled binaries in Fedora, we fault in an isolated
> error path (null pointer store followed by ud2 instruction).  I haven't
> had time to look at the disassembly and GIMPLE dumps to see what is
> going on.

I will create a testcase with a different struct msghdr sizes and alignment,
along with different timestamps configurations.

> 
>>> The disassembly suggests that GCC has detected some undefined behavior.
>>>
>>> This looks like a related bug:
>>>
>>>   __cmsg_nxthdr in cmsg_nxthdr.c (CMSG_NXTHDR) has undefined behavior when setting up ancillary data
>>>   <https://sourceware.org/bugzilla/show_bug.cgi?id=13500>
>>>
>>> I believe you cannot use CMSG_NXTHDR to append data in this way.
>>
>> I think I am getting luck here because the example provided does pass,
>> even when tested against valgrind, asan, and ubsan (using gcc 10).
> 
> Hmm.  I think it depends on previous buffer contents.

Ok, it might the case. But on my testing I also tried to add random 
initialization data on msghdr.

> 
>>> The other question is why this code is running at all.  Doing this
>>> complex conversion for a 32-bit applications doing a 32-bit function
>>> call on a kernel which supports 32-bit system calls does not make much
>>> sense to me.
>>
>> Mainly because kernel does not provide a 64-bit recvmsg, different than
>> recvmmg.  So for 64-bit time_t calls we need to do the conversion,
>> although it won't help much if the caller does not provide a buffer large
>> enough.
> 
> Yes, but the Fedora build does not use 64-bit time_t, so this conversion
> is kind of pointless there.> 
> I see that we didn't add a __recvmsg_time64 entrypoint (and similar
> entrypoints for the other cases).  I think that's a mistake, we should
> have those to future-proof things (similar for ioctl and fcntl—are there
> any other multiplexers?).
> 
>> I don't think we can improve it much by adding a 64-bit symbol: the
>> underlying syscall will be the same we don't prior hand which
>> SO_TIMESTAMP value were used to setup the timer (32-bit or 64-bit
>> one).
> 
> Yes, but old 32-bit binaries can avoid running the new code if we have
> separate entrypoint.

There are two fold problems here:

  1. With __ASSUME_TIME64_SYSCALLS, the SO_* timestamp constants being used
     will always 64-bit.  It means we will need translation for programs
     built *without* _TIME_BITS=64.

  2. Without __ASSUME_TIME64_SYSCALLS, we can't know what kind of timestamp
     is being configured on the socket.  The setsockopt() will still maps
     to old 32-bit timestamps for ENOPROTOOPT.

A new 64-bit entrypoint does not help much in my opinion: we can't know
for sure if the socket has being configured with an old library or either
pass with 32-bit timestamps with SCM_RIGHTS.

> 
>> Revising the code I found one issue with __convert_scm_timestamps,
>> where the memcpy might indeed being accessing invalid memory:
>>
>> diff --git a/sysdeps/unix/sysv/linux/convert_scm_timestamps.c b/sysdeps/unix/sysv/linux/convert_scm_timestamps.c
>> index d75a4618dd..2c61267fec 100644
>> --- a/sysdeps/unix/sysv/linux/convert_scm_timestamps.c
>> +++ b/sysdeps/unix/sysv/linux/convert_scm_timestamps.c
>> @@ -44,7 +44,8 @@ __convert_scm_timestamps (struct msghdr *msg, socklen_t msgsize)
>>       'struct __kernel_sock_timeval' while for SO_TIMESTAMPNS_NEW is a
>>       'struct __kernel_timespec'.  In either case it is two uint64_t
>>       members.  */
>> -  uint64_t tvts[2];
>> +  int64_t tvts[2];
>> +  int32_t tmp;
>>  
>>    struct cmsghdr *cmsg, *last = NULL;
>>    int type = 0;
>> @@ -69,7 +70,10 @@ __convert_scm_timestamps (struct msghdr *msg, socklen_t msgsize)
>>  
>>         /* fallthrough  */
>>         common:
>> -         memcpy (tvts, CMSG_DATA (cmsg), sizeof (tvts));
>> +         memcpy (&tmp, CMSG_DATA (cmsg), sizeof (tmp));
>> +         tvts[0] = tmp;
>> +         memcpy (&tmp, CMSG_DATA (cmsg) + sizeof (tmp), sizeof (tmp));
>> +         tvts[1] = tmp;
>>           break;
>>         }
> 
> Sorry, I can't quite wrap my head around this right now.
> 
> Thanks,
> Florian
>
diff mbox series

Patch

diff --git a/include/sys/socket.h b/include/sys/socket.h
index 0e39dd2a3a..15d4a62b26 100644
--- a/include/sys/socket.h
+++ b/include/sys/socket.h
@@ -164,5 +164,10 @@  libc_hidden_proto (__libc_sa_len)
 
 libc_hidden_proto (__cmsg_nxthdr)
 
+#ifndef __ASSUME_TIME64_SYSCALLS
+extern void __convert_scm_timestamps (struct msghdr *msg, socklen_t msgsize) ;
+libc_hidden_proto (__convert_scm_timestamps)
+#endif
+
 #endif
 #endif
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index bc14f20274..36d5ae020c 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -64,7 +64,7 @@  sysdep_routines += adjtimex clone umount umount2 readahead sysctl \
 		   time64-support pselect32 \
 		   xstat fxstat lxstat xstat64 fxstat64 lxstat64 \
 		   fxstatat fxstatat64 \
-		   xmknod xmknodat
+		   xmknod xmknodat convert_scm_timestamps
 
 CFLAGS-gethostid.c = -fexceptions
 CFLAGS-tee.c = -fexceptions -fasynchronous-unwind-tables
diff --git a/sysdeps/unix/sysv/linux/Versions b/sysdeps/unix/sysv/linux/Versions
index c864ad38ca..4637fd651d 100644
--- a/sysdeps/unix/sysv/linux/Versions
+++ b/sysdeps/unix/sysv/linux/Versions
@@ -194,6 +194,7 @@  libc {
     __pread64_nocancel;
     __close_nocancel;
     __sigtimedwait;
+    __convert_scm_timestamps;
     # functions used by nscd
     __netlink_assert_response;
   }
diff --git a/sysdeps/unix/sysv/linux/convert_scm_timestamps.c b/sysdeps/unix/sysv/linux/convert_scm_timestamps.c
new file mode 100644
index 0000000000..3c123c28ce
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/convert_scm_timestamps.c
@@ -0,0 +1,96 @@ 
+/* Socket timestamp conversion routines.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <kernel-features.h>
+
+#ifndef __ASSUME_TIME64_SYSCALLS
+# include <stdint.h>
+# include <string.h>
+# include <sys/socket.h>
+# include <socket-constants-time64.h>
+
+/* It converts the first SO_TIMESTAMP or SO_TIMESTAMPNS with 32-bit time and
+   appends it to the control buffer.  The 32-bit time field is kept as-is.
+
+   Calls with __TIMESIZE=32 will see the converted 64-bit time control
+   messages as spurious control message of unknown type.
+
+   Calls with __TIMESIZE=64 running on pre-time64 kernels will see the
+   original message as a spurious control ones of unknown typ while running
+   on kernel with native 64-bit time support will only see the time64 version
+   of the control message.  */
+void
+__convert_scm_timestamps (struct msghdr *msg, socklen_t msgsize)
+{
+  if (msg->msg_control == NULL || msg->msg_controllen == 0)
+    return;
+
+  /* The returned control message format for SO_TIMESTAMP_NEW is a
+     'struct __kernel_sock_timeval' while for SO_TIMESTAMPNS_NEW is a
+     'struct __kernel_timespec'.  In either case it is two uint64_t
+     members.  */
+  uint64_t tvts[2];
+
+  struct cmsghdr *cmsg, *last = NULL;
+  int type = 0;
+
+  for (cmsg = CMSG_FIRSTHDR (msg);
+       cmsg != NULL;
+       cmsg = CMSG_NXTHDR (msg, cmsg))
+    {
+      if (cmsg->cmsg_level != SOL_SOCKET)
+	continue;
+
+      switch (cmsg->cmsg_type)
+	{
+	case COMPAT_SO_TIMESTAMP_OLD:
+	  if (type != 0)
+	    break;
+	  type = COMPAT_SO_TIMESTAMP_NEW;
+	  goto common;
+
+	case COMPAT_SO_TIMESTAMPNS_OLD:
+	  type = COMPAT_SO_TIMESTAMPNS_NEW;
+
+	/* fallthrough  */
+	common:
+	  memcpy (tvts, CMSG_DATA (cmsg), sizeof (tvts));
+	  break;
+	}
+
+      last = cmsg;
+    }
+
+  if (last == NULL || type == 0)
+    return;
+
+  if (CMSG_SPACE (sizeof tvts) > msgsize - msg->msg_controllen)
+    {
+      msg->msg_flags |= MSG_CTRUNC;
+      return;
+    }
+
+  msg->msg_controllen += CMSG_SPACE (sizeof tvts);
+  cmsg = CMSG_NXTHDR(msg, last);
+  cmsg->cmsg_level = SOL_SOCKET;
+  cmsg->cmsg_type = type;
+  cmsg->cmsg_len = CMSG_LEN (sizeof tvts);
+  memcpy (CMSG_DATA (cmsg), tvts, sizeof tvts);
+}
+libc_hidden_def (__convert_scm_timestamps)
+#endif
diff --git a/sysdeps/unix/sysv/linux/getsockopt.c b/sysdeps/unix/sysv/linux/getsockopt.c
index 688a7de087..f86b06dec6 100644
--- a/sysdeps/unix/sysv/linux/getsockopt.c
+++ b/sysdeps/unix/sysv/linux/getsockopt.c
@@ -70,6 +70,18 @@  getsockopt32 (int fd, int level, int optname, void *optval,
 	else
 	  memcpy (optval, &tv32, sizeof tv32);
       }
+      break;
+
+    case COMPAT_SO_TIMESTAMP_NEW:
+    case COMPAT_SO_TIMESTAMPNS_NEW:
+      {
+	if (optname == COMPAT_SO_TIMESTAMP_NEW)
+	  optname = COMPAT_SO_TIMESTAMP_OLD;
+	if (optname == COMPAT_SO_TIMESTAMPNS_NEW)
+	  optname = COMPAT_SO_TIMESTAMPNS_OLD;
+	r = getsockopt_syscall (fd, level, optname, optval, len);
+      }
+      break;
     }
 
   return r;
diff --git a/sysdeps/unix/sysv/linux/hppa/socket-constants-time64.h b/sysdeps/unix/sysv/linux/hppa/socket-constants-time64.h
index 9fe7576aaa..0a48990ad8 100644
--- a/sysdeps/unix/sysv/linux/hppa/socket-constants-time64.h
+++ b/sysdeps/unix/sysv/linux/hppa/socket-constants-time64.h
@@ -27,4 +27,9 @@ 
 #define COMPAT_SO_RCVTIMEO_NEW 16448
 #define COMPAT_SO_SNDTIMEO_NEW 16449
 
+#define COMPAT_SO_TIMESTAMP_OLD 0x4012
+#define COMPAT_SO_TIMESTAMPNS_OLD 0x4013
+#define COMPAT_SO_TIMESTAMP_NEW 0x4038
+#define COMPAT_SO_TIMESTAMPNS_NEW 0x4039
+
 #endif
diff --git a/sysdeps/unix/sysv/linux/mips/socket-constants-time64.h b/sysdeps/unix/sysv/linux/mips/socket-constants-time64.h
index 1252a8a23b..728f7aa456 100644
--- a/sysdeps/unix/sysv/linux/mips/socket-constants-time64.h
+++ b/sysdeps/unix/sysv/linux/mips/socket-constants-time64.h
@@ -27,4 +27,9 @@ 
 #define COMPAT_SO_RCVTIMEO_NEW 66
 #define COMPAT_SO_SNDTIMEO_NEW 67
 
+#define COMPAT_SO_TIMESTAMP_OLD 29
+#define COMPAT_SO_TIMESTAMPNS_OLD 35
+#define COMPAT_SO_TIMESTAMP_NEW 63
+#define COMPAT_SO_TIMESTAMPNS_NEW 64
+
 #endif
diff --git a/sysdeps/unix/sysv/linux/powerpc/socket-constants-time64.h b/sysdeps/unix/sysv/linux/powerpc/socket-constants-time64.h
index 26e8b710ab..c231b7285a 100644
--- a/sysdeps/unix/sysv/linux/powerpc/socket-constants-time64.h
+++ b/sysdeps/unix/sysv/linux/powerpc/socket-constants-time64.h
@@ -27,4 +27,9 @@ 
 #define COMPAT_SO_RCVTIMEO_NEW 66
 #define COMPAT_SO_SNDTIMEO_NEW 67
 
+#define COMPAT_SO_TIMESTAMP_OLD 29
+#define COMPAT_SO_TIMESTAMPNS_OLD 35
+#define COMPAT_SO_TIMESTAMP_NEW 63
+#define COMPAT_SO_TIMESTAMPNS_NEW 64
+
 #endif
diff --git a/sysdeps/unix/sysv/linux/recvmsg.c b/sysdeps/unix/sysv/linux/recvmsg.c
index b209b4ad99..a2a600228b 100644
--- a/sysdeps/unix/sysv/linux/recvmsg.c
+++ b/sysdeps/unix/sysv/linux/recvmsg.c
@@ -19,16 +19,27 @@ 
 #include <sys/socket.h>
 #include <sysdep-cancel.h>
 #include <socketcall.h>
-#include <shlib-compat.h>
 
 ssize_t
 __libc_recvmsg (int fd, struct msghdr *msg, int flags)
 {
-# ifdef __ASSUME_RECVMSG_SYSCALL
-  return SYSCALL_CANCEL (recvmsg, fd, msg, flags);
-# else
-  return SOCKETCALL_CANCEL (recvmsg, fd, msg, flags);
-# endif
+  ssize_t r;
+#ifndef __ASSUME_TIME64_SYSCALLS
+  socklen_t orig_controllen = msg->msg_controllen;
+#endif
+
+#ifdef __ASSUME_RECVMSG_SYSCALL
+  r = SYSCALL_CANCEL (recvmsg, fd, msg, flags);
+#else
+  r = SOCKETCALL_CANCEL (recvmsg, fd, msg, flags);
+#endif
+
+#ifndef __ASSUME_TIME64_SYSCALLS
+  if (r >= 0)
+    __convert_scm_timestamps (msg, orig_controllen);
+#endif
+
+  return r;
 }
 weak_alias (__libc_recvmsg, recvmsg)
 weak_alias (__libc_recvmsg, __recvmsg)
diff --git a/sysdeps/unix/sysv/linux/setsockopt.c b/sysdeps/unix/sysv/linux/setsockopt.c
index 6505202265..a4780a9d33 100644
--- a/sysdeps/unix/sysv/linux/setsockopt.c
+++ b/sysdeps/unix/sysv/linux/setsockopt.c
@@ -69,6 +69,18 @@  setsockopt32 (int fd, int level, int optname, const void *optval,
 
 	r = setsockopt_syscall (fd, level, optname, &tv32, sizeof (tv32));
       }
+      break;
+
+    case COMPAT_SO_TIMESTAMP_NEW:
+    case COMPAT_SO_TIMESTAMPNS_NEW:
+      {
+	if (optname == COMPAT_SO_TIMESTAMP_NEW)
+	  optname = COMPAT_SO_TIMESTAMP_OLD;
+	if (optname == COMPAT_SO_TIMESTAMPNS_NEW)
+	  optname = COMPAT_SO_TIMESTAMPNS_OLD;
+	r = setsockopt_syscall (fd, level, optname, NULL, 0);
+      }
+      break;
     }
 
   return r;
diff --git a/sysdeps/unix/sysv/linux/socket-constants-time64.h b/sysdeps/unix/sysv/linux/socket-constants-time64.h
index d09c39d6c2..6cb249cc32 100644
--- a/sysdeps/unix/sysv/linux/socket-constants-time64.h
+++ b/sysdeps/unix/sysv/linux/socket-constants-time64.h
@@ -27,4 +27,9 @@ 
 #define COMPAT_SO_RCVTIMEO_NEW 66
 #define COMPAT_SO_SNDTIMEO_NEW 67
 
+#define COMPAT_SO_TIMESTAMP_OLD 29
+#define COMPAT_SO_TIMESTAMPNS_OLD 35
+#define COMPAT_SO_TIMESTAMP_NEW 63
+#define COMPAT_SO_TIMESTAMPNS_NEW 64
+
 #endif
diff --git a/sysdeps/unix/sysv/linux/sparc/socket-constants-time64.h b/sysdeps/unix/sysv/linux/sparc/socket-constants-time64.h
index f4668db537..4242cb6d19 100644
--- a/sysdeps/unix/sysv/linux/sparc/socket-constants-time64.h
+++ b/sysdeps/unix/sysv/linux/sparc/socket-constants-time64.h
@@ -27,4 +27,9 @@ 
 #define COMPAT_SO_RCVTIMEO_NEW 68
 #define COMPAT_SO_SNDTIMEO_NEW 69
 
+#define COMPAT_SO_TIMESTAMP_OLD 0x001d
+#define COMPAT_SO_TIMESTAMPNS_OLD 0x0021
+#define COMPAT_SO_TIMESTAMP_NEW 0x0046
+#define COMPAT_SO_TIMESTAMPNS_NEW 0x0042
+
 #endif