aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArjun Shankar <arjun@redhat.com>2022-08-02 11:10:25 +0200
committerAndreas K. Hüttel <dilfridge@gentoo.org>2022-09-03 22:26:04 +0200
commit252614a0d41b334c477549967fd7201456584ef6 (patch)
tree0a98945d78150b70711a133034652e0c07e5da3f /sysdeps
parentalpha: Fix generic brk system call emulation in __brk_call (bug 29490) (diff)
downloadglibc-252614a0d41b334c477549967fd7201456584ef6.tar.gz
glibc-252614a0d41b334c477549967fd7201456584ef6.tar.bz2
glibc-252614a0d41b334c477549967fd7201456584ef6.zip
socket: Check lengths before advancing pointer in CMSG_NXTHDR
The inline and library functions that the CMSG_NXTHDR macro may expand to increment the pointer to the header before checking the stride of the increment against available space. Since C only allows incrementing pointers to one past the end of an array, the increment must be done after a length check. This commit fixes that and includes a regression test for CMSG_FIRSTHDR and CMSG_NXTHDR. The Linux, Hurd, and generic headers are all changed. Tested on Linux on armv7hl, i686, x86_64, aarch64, ppc64le, and s390x. [BZ #28846] Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org> (cherry picked from commit 9c443ac4559a47ed99859bd80d14dc4b6dd220a1) (cherry picked from commit cbd8685e82d1b4a7160250845940be68510455ac)
Diffstat (limited to 'sysdeps')
-rw-r--r--sysdeps/mach/hurd/bits/socket.h40
-rw-r--r--sysdeps/unix/sysv/linux/bits/socket.h40
-rw-r--r--sysdeps/unix/sysv/linux/cmsg_nxthdr.c36
3 files changed, 94 insertions, 22 deletions
diff --git a/sysdeps/mach/hurd/bits/socket.h b/sysdeps/mach/hurd/bits/socket.h
index 5b35ea81ec..70fce4fb27 100644
--- a/sysdeps/mach/hurd/bits/socket.h
+++ b/sysdeps/mach/hurd/bits/socket.h
@@ -249,6 +249,12 @@ struct cmsghdr
+ CMSG_ALIGN (sizeof (struct cmsghdr)))
#define CMSG_LEN(len) (CMSG_ALIGN (sizeof (struct cmsghdr)) + (len))
+/* Given a length, return the additional padding necessary such that
+ len + __CMSG_PADDING(len) == CMSG_ALIGN (len). */
+#define __CMSG_PADDING(len) ((sizeof (size_t) \
+ - ((len) & (sizeof (size_t) - 1))) \
+ & (sizeof (size_t) - 1))
+
extern struct cmsghdr *__cmsg_nxthdr (struct msghdr *__mhdr,
struct cmsghdr *__cmsg) __THROW;
#ifdef __USE_EXTERN_INLINES
@@ -258,18 +264,38 @@ extern struct cmsghdr *__cmsg_nxthdr (struct msghdr *__mhdr,
_EXTERN_INLINE struct cmsghdr *
__NTH (__cmsg_nxthdr (struct msghdr *__mhdr, struct cmsghdr *__cmsg))
{
+ /* We may safely assume that __cmsg lies between __mhdr->msg_control and
+ __mhdr->msg_controllen because the user is required to obtain the first
+ cmsg via CMSG_FIRSTHDR, set its length, then obtain subsequent cmsgs
+ via CMSG_NXTHDR, setting lengths along the way. However, we don't yet
+ trust the value of __cmsg->cmsg_len and therefore do not use it in any
+ pointer arithmetic until we check its value. */
+
+ unsigned char * __msg_control_ptr = (unsigned char *) __mhdr->msg_control;
+ unsigned char * __cmsg_ptr = (unsigned char *) __cmsg;
+
+ size_t __size_needed = sizeof (struct cmsghdr)
+ + __CMSG_PADDING (__cmsg->cmsg_len);
+
+ /* The current header is malformed, too small to be a full header. */
if ((size_t) __cmsg->cmsg_len < sizeof (struct cmsghdr))
- /* The kernel header does this so there may be a reason. */
return (struct cmsghdr *) 0;
+ /* There isn't enough space between __cmsg and the end of the buffer to
+ hold the current cmsg *and* the next one. */
+ if (((size_t)
+ (__msg_control_ptr + __mhdr->msg_controllen - __cmsg_ptr)
+ < __size_needed)
+ || ((size_t)
+ (__msg_control_ptr + __mhdr->msg_controllen - __cmsg_ptr
+ - __size_needed)
+ < __cmsg->cmsg_len))
+
+ return (struct cmsghdr *) 0;
+
+ /* Now, we trust cmsg_len and can use it to find the next header. */
__cmsg = (struct cmsghdr *) ((unsigned char *) __cmsg
+ CMSG_ALIGN (__cmsg->cmsg_len));
- if ((unsigned char *) (__cmsg + 1) > ((unsigned char *) __mhdr->msg_control
- + __mhdr->msg_controllen)
- || ((unsigned char *) __cmsg + CMSG_ALIGN (__cmsg->cmsg_len)
- > ((unsigned char *) __mhdr->msg_control + __mhdr->msg_controllen)))
- /* No more entries. */
- return (struct cmsghdr *) 0;
return __cmsg;
}
#endif /* Use `extern inline'. */
diff --git a/sysdeps/unix/sysv/linux/bits/socket.h b/sysdeps/unix/sysv/linux/bits/socket.h
index 88fce77349..1dd7356a16 100644
--- a/sysdeps/unix/sysv/linux/bits/socket.h
+++ b/sysdeps/unix/sysv/linux/bits/socket.h
@@ -306,6 +306,12 @@ struct cmsghdr
+ CMSG_ALIGN (sizeof (struct cmsghdr)))
#define CMSG_LEN(len) (CMSG_ALIGN (sizeof (struct cmsghdr)) + (len))
+/* Given a length, return the additional padding necessary such that
+ len + __CMSG_PADDING(len) == CMSG_ALIGN (len). */
+#define __CMSG_PADDING(len) ((sizeof (size_t) \
+ - ((len) & (sizeof (size_t) - 1))) \
+ & (sizeof (size_t) - 1))
+
extern struct cmsghdr *__cmsg_nxthdr (struct msghdr *__mhdr,
struct cmsghdr *__cmsg) __THROW;
#ifdef __USE_EXTERN_INLINES
@@ -315,18 +321,38 @@ extern struct cmsghdr *__cmsg_nxthdr (struct msghdr *__mhdr,
_EXTERN_INLINE struct cmsghdr *
__NTH (__cmsg_nxthdr (struct msghdr *__mhdr, struct cmsghdr *__cmsg))
{
+ /* We may safely assume that __cmsg lies between __mhdr->msg_control and
+ __mhdr->msg_controllen because the user is required to obtain the first
+ cmsg via CMSG_FIRSTHDR, set its length, then obtain subsequent cmsgs
+ via CMSG_NXTHDR, setting lengths along the way. However, we don't yet
+ trust the value of __cmsg->cmsg_len and therefore do not use it in any
+ pointer arithmetic until we check its value. */
+
+ unsigned char * __msg_control_ptr = (unsigned char *) __mhdr->msg_control;
+ unsigned char * __cmsg_ptr = (unsigned char *) __cmsg;
+
+ size_t __size_needed = sizeof (struct cmsghdr)
+ + __CMSG_PADDING (__cmsg->cmsg_len);
+
+ /* The current header is malformed, too small to be a full header. */
if ((size_t) __cmsg->cmsg_len < sizeof (struct cmsghdr))
- /* The kernel header does this so there may be a reason. */
return (struct cmsghdr *) 0;
+ /* There isn't enough space between __cmsg and the end of the buffer to
+ hold the current cmsg *and* the next one. */
+ if (((size_t)
+ (__msg_control_ptr + __mhdr->msg_controllen - __cmsg_ptr)
+ < __size_needed)
+ || ((size_t)
+ (__msg_control_ptr + __mhdr->msg_controllen - __cmsg_ptr
+ - __size_needed)
+ < __cmsg->cmsg_len))
+
+ return (struct cmsghdr *) 0;
+
+ /* Now, we trust cmsg_len and can use it to find the next header. */
__cmsg = (struct cmsghdr *) ((unsigned char *) __cmsg
+ CMSG_ALIGN (__cmsg->cmsg_len));
- if ((unsigned char *) (__cmsg + 1) > ((unsigned char *) __mhdr->msg_control
- + __mhdr->msg_controllen)
- || ((unsigned char *) __cmsg + CMSG_ALIGN (__cmsg->cmsg_len)
- > ((unsigned char *) __mhdr->msg_control + __mhdr->msg_controllen)))
- /* No more entries. */
- return (struct cmsghdr *) 0;
return __cmsg;
}
#endif /* Use `extern inline'. */
diff --git a/sysdeps/unix/sysv/linux/cmsg_nxthdr.c b/sysdeps/unix/sysv/linux/cmsg_nxthdr.c
index 15b7a3a925..24f72b797a 100644
--- a/sysdeps/unix/sysv/linux/cmsg_nxthdr.c
+++ b/sysdeps/unix/sysv/linux/cmsg_nxthdr.c
@@ -23,18 +23,38 @@
struct cmsghdr *
__cmsg_nxthdr (struct msghdr *mhdr, struct cmsghdr *cmsg)
{
+ /* We may safely assume that cmsg lies between mhdr->msg_control and
+ mhdr->msg_controllen because the user is required to obtain the first
+ cmsg via CMSG_FIRSTHDR, set its length, then obtain subsequent cmsgs
+ via CMSG_NXTHDR, setting lengths along the way. However, we don't yet
+ trust the value of cmsg->cmsg_len and therefore do not use it in any
+ pointer arithmetic until we check its value. */
+
+ unsigned char * msg_control_ptr = (unsigned char *) mhdr->msg_control;
+ unsigned char * cmsg_ptr = (unsigned char *) cmsg;
+
+ size_t size_needed = sizeof (struct cmsghdr)
+ + __CMSG_PADDING (cmsg->cmsg_len);
+
+ /* The current header is malformed, too small to be a full header. */
if ((size_t) cmsg->cmsg_len < sizeof (struct cmsghdr))
- /* The kernel header does this so there may be a reason. */
- return NULL;
+ return (struct cmsghdr *) 0;
+
+ /* There isn't enough space between cmsg and the end of the buffer to
+ hold the current cmsg *and* the next one. */
+ if (((size_t)
+ (msg_control_ptr + mhdr->msg_controllen - cmsg_ptr)
+ < size_needed)
+ || ((size_t)
+ (msg_control_ptr + mhdr->msg_controllen - cmsg_ptr
+ - size_needed)
+ < cmsg->cmsg_len))
+
+ return (struct cmsghdr *) 0;
+ /* Now, we trust cmsg_len and can use it to find the next header. */
cmsg = (struct cmsghdr *) ((unsigned char *) cmsg
+ CMSG_ALIGN (cmsg->cmsg_len));
- if ((unsigned char *) (cmsg + 1) > ((unsigned char *) mhdr->msg_control
- + mhdr->msg_controllen)
- || ((unsigned char *) cmsg + CMSG_ALIGN (cmsg->cmsg_len)
- > ((unsigned char *) mhdr->msg_control + mhdr->msg_controllen)))
- /* No more entries. */
- return NULL;
return cmsg;
}
libc_hidden_def (__cmsg_nxthdr)