6ca6e8
commit 68507377f249d165f1f35502d96e9365edb07d9a
6ca6e8
Author: Arjun Shankar <arjun@redhat.com>
6ca6e8
Date:   Tue Aug 2 11:10:25 2022 +0200
6ca6e8
6ca6e8
    socket: Check lengths before advancing pointer in CMSG_NXTHDR
6ca6e8
    
6ca6e8
    The inline and library functions that the CMSG_NXTHDR macro may expand
6ca6e8
    to increment the pointer to the header before checking the stride of
6ca6e8
    the increment against available space.  Since C only allows incrementing
6ca6e8
    pointers to one past the end of an array, the increment must be done
6ca6e8
    after a length check.  This commit fixes that and includes a regression
6ca6e8
    test for CMSG_FIRSTHDR and CMSG_NXTHDR.
6ca6e8
    
6ca6e8
    The Linux, Hurd, and generic headers are all changed.
6ca6e8
    
6ca6e8
    Tested on Linux on armv7hl, i686, x86_64, aarch64, ppc64le, and s390x.
6ca6e8
    
6ca6e8
    [BZ #28846]
6ca6e8
    
6ca6e8
    Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
6ca6e8
    (cherry picked from commit 9c443ac4559a47ed99859bd80d14dc4b6dd220a1)
6ca6e8
6ca6e8
diff --git a/bits/socket.h b/bits/socket.h
6ca6e8
index 05ac0249c7da7218..781b1b2d1e0632a8 100644
6ca6e8
--- a/bits/socket.h
6ca6e8
+++ b/bits/socket.h
6ca6e8
@@ -245,6 +245,12 @@ struct cmsghdr
6ca6e8
 			 + CMSG_ALIGN (sizeof (struct cmsghdr)))
6ca6e8
 #define CMSG_LEN(len)   (CMSG_ALIGN (sizeof (struct cmsghdr)) + (len))
6ca6e8
 
6ca6e8
+/* Given a length, return the additional padding necessary such that
6ca6e8
+   len + __CMSG_PADDING(len) == CMSG_ALIGN (len).  */
6ca6e8
+#define __CMSG_PADDING(len) ((sizeof (size_t) \
6ca6e8
+                              - ((len) & (sizeof (size_t) - 1))) \
6ca6e8
+                             & (sizeof (size_t) - 1))
6ca6e8
+
6ca6e8
 extern struct cmsghdr *__cmsg_nxthdr (struct msghdr *__mhdr,
6ca6e8
 				      struct cmsghdr *__cmsg) __THROW;
6ca6e8
 #ifdef __USE_EXTERN_INLINES
6ca6e8
@@ -254,18 +260,38 @@ extern struct cmsghdr *__cmsg_nxthdr (struct msghdr *__mhdr,
6ca6e8
 _EXTERN_INLINE struct cmsghdr *
6ca6e8
 __NTH (__cmsg_nxthdr (struct msghdr *__mhdr, struct cmsghdr *__cmsg))
6ca6e8
 {
6ca6e8
+  /* We may safely assume that __cmsg lies between __mhdr->msg_control and
6ca6e8
+     __mhdr->msg_controllen because the user is required to obtain the first
6ca6e8
+     cmsg via CMSG_FIRSTHDR, set its length, then obtain subsequent cmsgs
6ca6e8
+     via CMSG_NXTHDR, setting lengths along the way.  However, we don't yet
6ca6e8
+     trust the value of __cmsg->cmsg_len and therefore do not use it in any
6ca6e8
+     pointer arithmetic until we check its value.  */
6ca6e8
+
6ca6e8
+  unsigned char * __msg_control_ptr = (unsigned char *) __mhdr->msg_control;
6ca6e8
+  unsigned char * __cmsg_ptr = (unsigned char *) __cmsg;
6ca6e8
+
6ca6e8
+  size_t __size_needed = sizeof (struct cmsghdr)
6ca6e8
+                         + __CMSG_PADDING (__cmsg->cmsg_len);
6ca6e8
+
6ca6e8
+  /* The current header is malformed, too small to be a full header.  */
6ca6e8
   if ((size_t) __cmsg->cmsg_len < sizeof (struct cmsghdr))
6ca6e8
-    /* The kernel header does this so there may be a reason.  */
6ca6e8
     return (struct cmsghdr *) 0;
6ca6e8
 
6ca6e8
+  /* There isn't enough space between __cmsg and the end of the buffer to
6ca6e8
+  hold the current cmsg *and* the next one.  */
6ca6e8
+  if (((size_t)
6ca6e8
+         (__msg_control_ptr + __mhdr->msg_controllen - __cmsg_ptr)
6ca6e8
+       < __size_needed)
6ca6e8
+      || ((size_t)
6ca6e8
+            (__msg_control_ptr + __mhdr->msg_controllen - __cmsg_ptr
6ca6e8
+             - __size_needed)
6ca6e8
+          < __cmsg->cmsg_len))
6ca6e8
+
6ca6e8
+    return (struct cmsghdr *) 0;
6ca6e8
+
6ca6e8
+  /* Now, we trust cmsg_len and can use it to find the next header.  */
6ca6e8
   __cmsg = (struct cmsghdr *) ((unsigned char *) __cmsg
6ca6e8
 			       + CMSG_ALIGN (__cmsg->cmsg_len));
6ca6e8
-  if ((unsigned char *) (__cmsg + 1) > ((unsigned char *) __mhdr->msg_control
6ca6e8
-					+ __mhdr->msg_controllen)
6ca6e8
-      || ((unsigned char *) __cmsg + CMSG_ALIGN (__cmsg->cmsg_len)
6ca6e8
-	  > ((unsigned char *) __mhdr->msg_control + __mhdr->msg_controllen)))
6ca6e8
-    /* No more entries.  */
6ca6e8
-    return (struct cmsghdr *) 0;
6ca6e8
   return __cmsg;
6ca6e8
 }
6ca6e8
 #endif	/* Use `extern inline'.  */
6ca6e8
diff --git a/socket/Makefile b/socket/Makefile
6ca6e8
index c2de11d73ca1e324..2fdf441bb44bf142 100644
6ca6e8
--- a/socket/Makefile
6ca6e8
+++ b/socket/Makefile
6ca6e8
@@ -34,6 +34,7 @@ routines := accept bind connect getpeername getsockname getsockopt	\
6ca6e8
 tests := \
6ca6e8
   tst-accept4 \
6ca6e8
   tst-sockopt \
6ca6e8
+  tst-cmsghdr \
6ca6e8
   # tests
6ca6e8
 
6ca6e8
 tests-internal := \
6ca6e8
diff --git a/socket/tst-cmsghdr-skeleton.c b/socket/tst-cmsghdr-skeleton.c
6ca6e8
new file mode 100644
6ca6e8
index 0000000000000000..7accfa6e54708e2a
6ca6e8
--- /dev/null
6ca6e8
+++ b/socket/tst-cmsghdr-skeleton.c
6ca6e8
@@ -0,0 +1,93 @@
6ca6e8
+/* Test ancillary data header creation.
6ca6e8
+   Copyright (C) 2022 Free Software Foundation, Inc.
6ca6e8
+   This file is part of the GNU C Library.
6ca6e8
+
6ca6e8
+   The GNU C Library is free software; you can redistribute it and/or
6ca6e8
+   modify it under the terms of the GNU Lesser General Public
6ca6e8
+   License as published by the Free Software Foundation; either
6ca6e8
+   version 2.1 of the License, or (at your option) any later version.
6ca6e8
+
6ca6e8
+   The GNU C Library is distributed in the hope that it will be useful,
6ca6e8
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
6ca6e8
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
6ca6e8
+   Lesser General Public License for more details.
6ca6e8
+
6ca6e8
+   You should have received a copy of the GNU Lesser General Public
6ca6e8
+   License along with the GNU C Library; if not, see
6ca6e8
+   <https://www.gnu.org/licenses/>.  */
6ca6e8
+
6ca6e8
+/* We use the preprocessor to generate the function/macro tests instead of
6ca6e8
+   using indirection because having all the macro expansions alongside
6ca6e8
+   each other lets the compiler warn us about suspicious pointer
6ca6e8
+   arithmetic across subsequent CMSG_{FIRST,NXT}HDR expansions.  */
6ca6e8
+
6ca6e8
+#include <stdint.h>
6ca6e8
+#include <stddef.h>
6ca6e8
+
6ca6e8
+#define RUN_TEST_CONCAT(suffix) run_test_##suffix
6ca6e8
+#define RUN_TEST_FUNCNAME(suffix) RUN_TEST_CONCAT (suffix)
6ca6e8
+
6ca6e8
+static void
6ca6e8
+RUN_TEST_FUNCNAME (CMSG_NXTHDR_IMPL) (void)
6ca6e8
+{
6ca6e8
+  struct msghdr m = {0};
6ca6e8
+  struct cmsghdr *cmsg;
6ca6e8
+  char cmsgbuf[3 * CMSG_SPACE (sizeof (PAYLOAD))] = {0};
6ca6e8
+
6ca6e8
+  m.msg_control = cmsgbuf;
6ca6e8
+  m.msg_controllen = sizeof (cmsgbuf);
6ca6e8
+
6ca6e8
+  /* First header should point to the start of the buffer.  */
6ca6e8
+  cmsg = CMSG_FIRSTHDR (&m);
6ca6e8
+  TEST_VERIFY_EXIT ((char *) cmsg == cmsgbuf);
6ca6e8
+
6ca6e8
+  /* If the first header length consumes the entire buffer, there is no
6ca6e8
+     space remaining for additional headers.  */
6ca6e8
+  cmsg->cmsg_len = sizeof (cmsgbuf);
6ca6e8
+  cmsg = CMSG_NXTHDR_IMPL (&m, cmsg);
6ca6e8
+  TEST_VERIFY_EXIT (cmsg == NULL);
6ca6e8
+
6ca6e8
+  /* The first header length is so big, using it would cause an overflow.  */
6ca6e8
+  cmsg = CMSG_FIRSTHDR (&m);
6ca6e8
+  TEST_VERIFY_EXIT ((char *) cmsg == cmsgbuf);
6ca6e8
+  cmsg->cmsg_len = SIZE_MAX;
6ca6e8
+  cmsg = CMSG_NXTHDR_IMPL (&m, cmsg);
6ca6e8
+  TEST_VERIFY_EXIT (cmsg == NULL);
6ca6e8
+
6ca6e8
+  /* The first header leaves just enough space to hold another header.  */
6ca6e8
+  cmsg = CMSG_FIRSTHDR (&m);
6ca6e8
+  TEST_VERIFY_EXIT ((char *) cmsg == cmsgbuf);
6ca6e8
+  cmsg->cmsg_len = sizeof (cmsgbuf) - sizeof (struct cmsghdr);
6ca6e8
+  cmsg = CMSG_NXTHDR_IMPL (&m, cmsg);
6ca6e8
+  TEST_VERIFY_EXIT (cmsg != NULL);
6ca6e8
+
6ca6e8
+  /* The first header leaves space but not enough for another header.  */
6ca6e8
+  cmsg = CMSG_FIRSTHDR (&m);
6ca6e8
+  TEST_VERIFY_EXIT ((char *) cmsg == cmsgbuf);
6ca6e8
+  cmsg->cmsg_len ++;
6ca6e8
+  cmsg = CMSG_NXTHDR_IMPL (&m, cmsg);
6ca6e8
+  TEST_VERIFY_EXIT (cmsg == NULL);
6ca6e8
+
6ca6e8
+  /* The second header leaves just enough space to hold another header.  */
6ca6e8
+  cmsg = CMSG_FIRSTHDR (&m);
6ca6e8
+  TEST_VERIFY_EXIT ((char *) cmsg == cmsgbuf);
6ca6e8
+  cmsg->cmsg_len = CMSG_LEN (sizeof (PAYLOAD));
6ca6e8
+  cmsg = CMSG_NXTHDR_IMPL (&m, cmsg);
6ca6e8
+  TEST_VERIFY_EXIT (cmsg != NULL);
6ca6e8
+  cmsg->cmsg_len = sizeof (cmsgbuf)
6ca6e8
+                   - CMSG_SPACE (sizeof (PAYLOAD)) /* First header.  */
6ca6e8
+                   - sizeof (struct cmsghdr);
6ca6e8
+  cmsg = CMSG_NXTHDR_IMPL (&m, cmsg);
6ca6e8
+  TEST_VERIFY_EXIT (cmsg != NULL);
6ca6e8
+
6ca6e8
+  /* The second header leaves space but not enough for another header.  */
6ca6e8
+  cmsg = CMSG_FIRSTHDR (&m);
6ca6e8
+  TEST_VERIFY_EXIT ((char *) cmsg == cmsgbuf);
6ca6e8
+  cmsg = CMSG_NXTHDR_IMPL (&m, cmsg);
6ca6e8
+  TEST_VERIFY_EXIT (cmsg != NULL);
6ca6e8
+  cmsg->cmsg_len ++;
6ca6e8
+  cmsg = CMSG_NXTHDR_IMPL (&m, cmsg);
6ca6e8
+  TEST_VERIFY_EXIT (cmsg == NULL);
6ca6e8
+
6ca6e8
+  return;
6ca6e8
+}
6ca6e8
diff --git a/socket/tst-cmsghdr.c b/socket/tst-cmsghdr.c
6ca6e8
new file mode 100644
6ca6e8
index 0000000000000000..68c96d3c9dd2bce8
6ca6e8
--- /dev/null
6ca6e8
+++ b/socket/tst-cmsghdr.c
6ca6e8
@@ -0,0 +1,56 @@
6ca6e8
+/* Test ancillary data header creation.
6ca6e8
+   Copyright (C) 2022 Free Software Foundation, Inc.
6ca6e8
+   This file is part of the GNU C Library.
6ca6e8
+
6ca6e8
+   The GNU C Library is free software; you can redistribute it and/or
6ca6e8
+   modify it under the terms of the GNU Lesser General Public
6ca6e8
+   License as published by the Free Software Foundation; either
6ca6e8
+   version 2.1 of the License, or (at your option) any later version.
6ca6e8
+
6ca6e8
+   The GNU C Library is distributed in the hope that it will be useful,
6ca6e8
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
6ca6e8
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
6ca6e8
+   Lesser General Public License for more details.
6ca6e8
+
6ca6e8
+   You should have received a copy of the GNU Lesser General Public
6ca6e8
+   License along with the GNU C Library; if not, see
6ca6e8
+   <https://www.gnu.org/licenses/>.  */
6ca6e8
+
6ca6e8
+#include <sys/socket.h>
6ca6e8
+#include <gnu/lib-names.h>
6ca6e8
+#include <support/xdlfcn.h>
6ca6e8
+#include <support/check.h>
6ca6e8
+
6ca6e8
+#define PAYLOAD "Hello, World!"
6ca6e8
+
6ca6e8
+/* CMSG_NXTHDR is a macro that calls an inline function defined in
6ca6e8
+   bits/socket.h.  In case the function cannot be inlined, libc.so carries
6ca6e8
+   a copy.  Both versions need to be tested.  */
6ca6e8
+
6ca6e8
+#define CMSG_NXTHDR_IMPL CMSG_NXTHDR
6ca6e8
+#include "tst-cmsghdr-skeleton.c"
6ca6e8
+#undef CMSG_NXTHDR_IMPL
6ca6e8
+
6ca6e8
+static struct cmsghdr * (* cmsg_nxthdr) (struct msghdr *, struct cmsghdr *);
6ca6e8
+
6ca6e8
+#define CMSG_NXTHDR_IMPL cmsg_nxthdr
6ca6e8
+#include "tst-cmsghdr-skeleton.c"
6ca6e8
+#undef CMSG_NXTHDR_IMPL
6ca6e8
+
6ca6e8
+static int
6ca6e8
+do_test (void)
6ca6e8
+{
6ca6e8
+  static void *handle;
6ca6e8
+
6ca6e8
+  run_test_CMSG_NXTHDR ();
6ca6e8
+
6ca6e8
+  handle = xdlopen (LIBC_SO, RTLD_LAZY);
6ca6e8
+  cmsg_nxthdr = (struct cmsghdr * (*) (struct msghdr *, struct cmsghdr *))
6ca6e8
+                  xdlsym (handle, "__cmsg_nxthdr");
6ca6e8
+
6ca6e8
+  run_test_cmsg_nxthdr ();
6ca6e8
+
6ca6e8
+  return 0;
6ca6e8
+}
6ca6e8
+
6ca6e8
+#include <support/test-driver.c>
6ca6e8
diff --git a/sysdeps/mach/hurd/bits/socket.h b/sysdeps/mach/hurd/bits/socket.h
6ca6e8
index 5210a7b44950e957..423eb2df09c3eef9 100644
6ca6e8
--- a/sysdeps/mach/hurd/bits/socket.h
6ca6e8
+++ b/sysdeps/mach/hurd/bits/socket.h
6ca6e8
@@ -249,6 +249,12 @@ struct cmsghdr
6ca6e8
 			 + CMSG_ALIGN (sizeof (struct cmsghdr)))
6ca6e8
 #define CMSG_LEN(len)   (CMSG_ALIGN (sizeof (struct cmsghdr)) + (len))
6ca6e8
 
6ca6e8
+/* Given a length, return the additional padding necessary such that
6ca6e8
+   len + __CMSG_PADDING(len) == CMSG_ALIGN (len).  */
6ca6e8
+#define __CMSG_PADDING(len) ((sizeof (size_t) \
6ca6e8
+                              - ((len) & (sizeof (size_t) - 1))) \
6ca6e8
+                             & (sizeof (size_t) - 1))
6ca6e8
+
6ca6e8
 extern struct cmsghdr *__cmsg_nxthdr (struct msghdr *__mhdr,
6ca6e8
 				      struct cmsghdr *__cmsg) __THROW;
6ca6e8
 #ifdef __USE_EXTERN_INLINES
6ca6e8
@@ -258,18 +264,38 @@ extern struct cmsghdr *__cmsg_nxthdr (struct msghdr *__mhdr,
6ca6e8
 _EXTERN_INLINE struct cmsghdr *
6ca6e8
 __NTH (__cmsg_nxthdr (struct msghdr *__mhdr, struct cmsghdr *__cmsg))
6ca6e8
 {
6ca6e8
+  /* We may safely assume that __cmsg lies between __mhdr->msg_control and
6ca6e8
+     __mhdr->msg_controllen because the user is required to obtain the first
6ca6e8
+     cmsg via CMSG_FIRSTHDR, set its length, then obtain subsequent cmsgs
6ca6e8
+     via CMSG_NXTHDR, setting lengths along the way.  However, we don't yet
6ca6e8
+     trust the value of __cmsg->cmsg_len and therefore do not use it in any
6ca6e8
+     pointer arithmetic until we check its value.  */
6ca6e8
+
6ca6e8
+  unsigned char * __msg_control_ptr = (unsigned char *) __mhdr->msg_control;
6ca6e8
+  unsigned char * __cmsg_ptr = (unsigned char *) __cmsg;
6ca6e8
+
6ca6e8
+  size_t __size_needed = sizeof (struct cmsghdr)
6ca6e8
+                         + __CMSG_PADDING (__cmsg->cmsg_len);
6ca6e8
+
6ca6e8
+  /* The current header is malformed, too small to be a full header.  */
6ca6e8
   if ((size_t) __cmsg->cmsg_len < sizeof (struct cmsghdr))
6ca6e8
-    /* The kernel header does this so there may be a reason.  */
6ca6e8
     return (struct cmsghdr *) 0;
6ca6e8
 
6ca6e8
+  /* There isn't enough space between __cmsg and the end of the buffer to
6ca6e8
+  hold the current cmsg *and* the next one.  */
6ca6e8
+  if (((size_t)
6ca6e8
+         (__msg_control_ptr + __mhdr->msg_controllen - __cmsg_ptr)
6ca6e8
+       < __size_needed)
6ca6e8
+      || ((size_t)
6ca6e8
+            (__msg_control_ptr + __mhdr->msg_controllen - __cmsg_ptr
6ca6e8
+             - __size_needed)
6ca6e8
+          < __cmsg->cmsg_len))
6ca6e8
+
6ca6e8
+    return (struct cmsghdr *) 0;
6ca6e8
+
6ca6e8
+  /* Now, we trust cmsg_len and can use it to find the next header.  */
6ca6e8
   __cmsg = (struct cmsghdr *) ((unsigned char *) __cmsg
6ca6e8
 			       + CMSG_ALIGN (__cmsg->cmsg_len));
6ca6e8
-  if ((unsigned char *) (__cmsg + 1) > ((unsigned char *) __mhdr->msg_control
6ca6e8
-					+ __mhdr->msg_controllen)
6ca6e8
-      || ((unsigned char *) __cmsg + CMSG_ALIGN (__cmsg->cmsg_len)
6ca6e8
-	  > ((unsigned char *) __mhdr->msg_control + __mhdr->msg_controllen)))
6ca6e8
-    /* No more entries.  */
6ca6e8
-    return (struct cmsghdr *) 0;
6ca6e8
   return __cmsg;
6ca6e8
 }
6ca6e8
 #endif	/* Use `extern inline'.  */
6ca6e8
diff --git a/sysdeps/unix/sysv/linux/bits/socket.h b/sysdeps/unix/sysv/linux/bits/socket.h
6ca6e8
index c81fab840918924e..7d56f877e0a73acb 100644
6ca6e8
--- a/sysdeps/unix/sysv/linux/bits/socket.h
6ca6e8
+++ b/sysdeps/unix/sysv/linux/bits/socket.h
6ca6e8
@@ -306,6 +306,12 @@ struct cmsghdr
6ca6e8
 			 + CMSG_ALIGN (sizeof (struct cmsghdr)))
6ca6e8
 #define CMSG_LEN(len)   (CMSG_ALIGN (sizeof (struct cmsghdr)) + (len))
6ca6e8
 
6ca6e8
+/* Given a length, return the additional padding necessary such that
6ca6e8
+   len + __CMSG_PADDING(len) == CMSG_ALIGN (len).  */
6ca6e8
+#define __CMSG_PADDING(len) ((sizeof (size_t) \
6ca6e8
+                              - ((len) & (sizeof (size_t) - 1))) \
6ca6e8
+                             & (sizeof (size_t) - 1))
6ca6e8
+
6ca6e8
 extern struct cmsghdr *__cmsg_nxthdr (struct msghdr *__mhdr,
6ca6e8
 				      struct cmsghdr *__cmsg) __THROW;
6ca6e8
 #ifdef __USE_EXTERN_INLINES
6ca6e8
@@ -315,18 +321,38 @@ extern struct cmsghdr *__cmsg_nxthdr (struct msghdr *__mhdr,
6ca6e8
 _EXTERN_INLINE struct cmsghdr *
6ca6e8
 __NTH (__cmsg_nxthdr (struct msghdr *__mhdr, struct cmsghdr *__cmsg))
6ca6e8
 {
6ca6e8
+  /* We may safely assume that __cmsg lies between __mhdr->msg_control and
6ca6e8
+     __mhdr->msg_controllen because the user is required to obtain the first
6ca6e8
+     cmsg via CMSG_FIRSTHDR, set its length, then obtain subsequent cmsgs
6ca6e8
+     via CMSG_NXTHDR, setting lengths along the way.  However, we don't yet
6ca6e8
+     trust the value of __cmsg->cmsg_len and therefore do not use it in any
6ca6e8
+     pointer arithmetic until we check its value.  */
6ca6e8
+
6ca6e8
+  unsigned char * __msg_control_ptr = (unsigned char *) __mhdr->msg_control;
6ca6e8
+  unsigned char * __cmsg_ptr = (unsigned char *) __cmsg;
6ca6e8
+
6ca6e8
+  size_t __size_needed = sizeof (struct cmsghdr)
6ca6e8
+                         + __CMSG_PADDING (__cmsg->cmsg_len);
6ca6e8
+
6ca6e8
+  /* The current header is malformed, too small to be a full header.  */
6ca6e8
   if ((size_t) __cmsg->cmsg_len < sizeof (struct cmsghdr))
6ca6e8
-    /* The kernel header does this so there may be a reason.  */
6ca6e8
     return (struct cmsghdr *) 0;
6ca6e8
 
6ca6e8
+  /* There isn't enough space between __cmsg and the end of the buffer to
6ca6e8
+  hold the current cmsg *and* the next one.  */
6ca6e8
+  if (((size_t)
6ca6e8
+         (__msg_control_ptr + __mhdr->msg_controllen - __cmsg_ptr)
6ca6e8
+       < __size_needed)
6ca6e8
+      || ((size_t)
6ca6e8
+            (__msg_control_ptr + __mhdr->msg_controllen - __cmsg_ptr
6ca6e8
+             - __size_needed)
6ca6e8
+          < __cmsg->cmsg_len))
6ca6e8
+
6ca6e8
+    return (struct cmsghdr *) 0;
6ca6e8
+
6ca6e8
+  /* Now, we trust cmsg_len and can use it to find the next header.  */
6ca6e8
   __cmsg = (struct cmsghdr *) ((unsigned char *) __cmsg
6ca6e8
 			       + CMSG_ALIGN (__cmsg->cmsg_len));
6ca6e8
-  if ((unsigned char *) (__cmsg + 1) > ((unsigned char *) __mhdr->msg_control
6ca6e8
-					+ __mhdr->msg_controllen)
6ca6e8
-      || ((unsigned char *) __cmsg + CMSG_ALIGN (__cmsg->cmsg_len)
6ca6e8
-	  > ((unsigned char *) __mhdr->msg_control + __mhdr->msg_controllen)))
6ca6e8
-    /* No more entries.  */
6ca6e8
-    return (struct cmsghdr *) 0;
6ca6e8
   return __cmsg;
6ca6e8
 }
6ca6e8
 #endif	/* Use `extern inline'.  */
6ca6e8
diff --git a/sysdeps/unix/sysv/linux/cmsg_nxthdr.c b/sysdeps/unix/sysv/linux/cmsg_nxthdr.c
6ca6e8
index a0fe49f28563d030..535d22e9a037b9a9 100644
6ca6e8
--- a/sysdeps/unix/sysv/linux/cmsg_nxthdr.c
6ca6e8
+++ b/sysdeps/unix/sysv/linux/cmsg_nxthdr.c
6ca6e8
@@ -23,18 +23,38 @@
6ca6e8
 struct cmsghdr *
6ca6e8
 __cmsg_nxthdr (struct msghdr *mhdr, struct cmsghdr *cmsg)
6ca6e8
 {
6ca6e8
+  /* We may safely assume that cmsg lies between mhdr->msg_control and
6ca6e8
+     mhdr->msg_controllen because the user is required to obtain the first
6ca6e8
+     cmsg via CMSG_FIRSTHDR, set its length, then obtain subsequent cmsgs
6ca6e8
+     via CMSG_NXTHDR, setting lengths along the way.  However, we don't yet
6ca6e8
+     trust the value of cmsg->cmsg_len and therefore do not use it in any
6ca6e8
+     pointer arithmetic until we check its value.  */
6ca6e8
+
6ca6e8
+  unsigned char * msg_control_ptr = (unsigned char *) mhdr->msg_control;
6ca6e8
+  unsigned char * cmsg_ptr = (unsigned char *) cmsg;
6ca6e8
+
6ca6e8
+  size_t size_needed = sizeof (struct cmsghdr)
6ca6e8
+                       + __CMSG_PADDING (cmsg->cmsg_len);
6ca6e8
+
6ca6e8
+  /* The current header is malformed, too small to be a full header.  */
6ca6e8
   if ((size_t) cmsg->cmsg_len < sizeof (struct cmsghdr))
6ca6e8
-    /* The kernel header does this so there may be a reason.  */
6ca6e8
-    return NULL;
6ca6e8
+    return (struct cmsghdr *) 0;
6ca6e8
+
6ca6e8
+  /* There isn't enough space between cmsg and the end of the buffer to
6ca6e8
+  hold the current cmsg *and* the next one.  */
6ca6e8
+  if (((size_t)
6ca6e8
+         (msg_control_ptr + mhdr->msg_controllen - cmsg_ptr)
6ca6e8
+       < size_needed)
6ca6e8
+      || ((size_t)
6ca6e8
+            (msg_control_ptr + mhdr->msg_controllen - cmsg_ptr
6ca6e8
+             - size_needed)
6ca6e8
+          < cmsg->cmsg_len))
6ca6e8
+
6ca6e8
+    return (struct cmsghdr *) 0;
6ca6e8
 
6ca6e8
+  /* Now, we trust cmsg_len and can use it to find the next header.  */
6ca6e8
   cmsg = (struct cmsghdr *) ((unsigned char *) cmsg
6ca6e8
 			     + CMSG_ALIGN (cmsg->cmsg_len));
6ca6e8
-  if ((unsigned char *) (cmsg + 1) > ((unsigned char *) mhdr->msg_control
6ca6e8
-				      + mhdr->msg_controllen)
6ca6e8
-      || ((unsigned char *) cmsg + CMSG_ALIGN (cmsg->cmsg_len)
6ca6e8
-	  > ((unsigned char *) mhdr->msg_control + mhdr->msg_controllen)))
6ca6e8
-    /* No more entries.  */
6ca6e8
-    return NULL;
6ca6e8
   return cmsg;
6ca6e8
 }
6ca6e8
 libc_hidden_def (__cmsg_nxthdr)