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 9c443ac455)
This commit is contained in:
Arjun Shankar 2022-08-02 11:10:25 +02:00
parent 1fcc7bfee2
commit 68507377f2
8 changed files with 279 additions and 30 deletions

1
NEWS
View File

@ -85,6 +85,7 @@ The following bugs are resolved with this release:
[28769] CVE-2021-3999: Off-by-one buffer overflow/underflow in getcwd()
[28770] CVE-2021-3998: Unexpected return value from realpath() for too long results
[28784] x86: crash in 32bit memset-sse2.s when the cache size can not be determined
[28846] CMSG_NXTHDR may trigger -Wstrict-overflow warning
[28850] linux: __get_nprocs_sched reads uninitialized memory from the stack
[28857] FAIL: elf/tst-audit24a
[28860] build: --enable-kernel=5.1.0 build fails because of missing

View File

@ -245,6 +245,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
@ -254,18 +260,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'. */

View File

@ -34,6 +34,7 @@ routines := accept bind connect getpeername getsockname getsockopt \
tests := \
tst-accept4 \
tst-sockopt \
tst-cmsghdr \
# tests
tests-internal := \

View File

@ -0,0 +1,93 @@
/* Test ancillary data header creation.
Copyright (C) 2022 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/>. */
/* We use the preprocessor to generate the function/macro tests instead of
using indirection because having all the macro expansions alongside
each other lets the compiler warn us about suspicious pointer
arithmetic across subsequent CMSG_{FIRST,NXT}HDR expansions. */
#include <stdint.h>
#include <stddef.h>
#define RUN_TEST_CONCAT(suffix) run_test_##suffix
#define RUN_TEST_FUNCNAME(suffix) RUN_TEST_CONCAT (suffix)
static void
RUN_TEST_FUNCNAME (CMSG_NXTHDR_IMPL) (void)
{
struct msghdr m = {0};
struct cmsghdr *cmsg;
char cmsgbuf[3 * CMSG_SPACE (sizeof (PAYLOAD))] = {0};
m.msg_control = cmsgbuf;
m.msg_controllen = sizeof (cmsgbuf);
/* First header should point to the start of the buffer. */
cmsg = CMSG_FIRSTHDR (&m);
TEST_VERIFY_EXIT ((char *) cmsg == cmsgbuf);
/* If the first header length consumes the entire buffer, there is no
space remaining for additional headers. */
cmsg->cmsg_len = sizeof (cmsgbuf);
cmsg = CMSG_NXTHDR_IMPL (&m, cmsg);
TEST_VERIFY_EXIT (cmsg == NULL);
/* The first header length is so big, using it would cause an overflow. */
cmsg = CMSG_FIRSTHDR (&m);
TEST_VERIFY_EXIT ((char *) cmsg == cmsgbuf);
cmsg->cmsg_len = SIZE_MAX;
cmsg = CMSG_NXTHDR_IMPL (&m, cmsg);
TEST_VERIFY_EXIT (cmsg == NULL);
/* The first header leaves just enough space to hold another header. */
cmsg = CMSG_FIRSTHDR (&m);
TEST_VERIFY_EXIT ((char *) cmsg == cmsgbuf);
cmsg->cmsg_len = sizeof (cmsgbuf) - sizeof (struct cmsghdr);
cmsg = CMSG_NXTHDR_IMPL (&m, cmsg);
TEST_VERIFY_EXIT (cmsg != NULL);
/* The first header leaves space but not enough for another header. */
cmsg = CMSG_FIRSTHDR (&m);
TEST_VERIFY_EXIT ((char *) cmsg == cmsgbuf);
cmsg->cmsg_len ++;
cmsg = CMSG_NXTHDR_IMPL (&m, cmsg);
TEST_VERIFY_EXIT (cmsg == NULL);
/* The second header leaves just enough space to hold another header. */
cmsg = CMSG_FIRSTHDR (&m);
TEST_VERIFY_EXIT ((char *) cmsg == cmsgbuf);
cmsg->cmsg_len = CMSG_LEN (sizeof (PAYLOAD));
cmsg = CMSG_NXTHDR_IMPL (&m, cmsg);
TEST_VERIFY_EXIT (cmsg != NULL);
cmsg->cmsg_len = sizeof (cmsgbuf)
- CMSG_SPACE (sizeof (PAYLOAD)) /* First header. */
- sizeof (struct cmsghdr);
cmsg = CMSG_NXTHDR_IMPL (&m, cmsg);
TEST_VERIFY_EXIT (cmsg != NULL);
/* The second header leaves space but not enough for another header. */
cmsg = CMSG_FIRSTHDR (&m);
TEST_VERIFY_EXIT ((char *) cmsg == cmsgbuf);
cmsg = CMSG_NXTHDR_IMPL (&m, cmsg);
TEST_VERIFY_EXIT (cmsg != NULL);
cmsg->cmsg_len ++;
cmsg = CMSG_NXTHDR_IMPL (&m, cmsg);
TEST_VERIFY_EXIT (cmsg == NULL);
return;
}

56
socket/tst-cmsghdr.c Normal file
View File

@ -0,0 +1,56 @@
/* Test ancillary data header creation.
Copyright (C) 2022 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 <sys/socket.h>
#include <gnu/lib-names.h>
#include <support/xdlfcn.h>
#include <support/check.h>
#define PAYLOAD "Hello, World!"
/* CMSG_NXTHDR is a macro that calls an inline function defined in
bits/socket.h. In case the function cannot be inlined, libc.so carries
a copy. Both versions need to be tested. */
#define CMSG_NXTHDR_IMPL CMSG_NXTHDR
#include "tst-cmsghdr-skeleton.c"
#undef CMSG_NXTHDR_IMPL
static struct cmsghdr * (* cmsg_nxthdr) (struct msghdr *, struct cmsghdr *);
#define CMSG_NXTHDR_IMPL cmsg_nxthdr
#include "tst-cmsghdr-skeleton.c"
#undef CMSG_NXTHDR_IMPL
static int
do_test (void)
{
static void *handle;
run_test_CMSG_NXTHDR ();
handle = xdlopen (LIBC_SO, RTLD_LAZY);
cmsg_nxthdr = (struct cmsghdr * (*) (struct msghdr *, struct cmsghdr *))
xdlsym (handle, "__cmsg_nxthdr");
run_test_cmsg_nxthdr ();
return 0;
}
#include <support/test-driver.c>

View File

@ -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'. */

View File

@ -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'. */

View File

@ -23,18 +23,38 @@
struct cmsghdr *
__cmsg_nxthdr (struct msghdr *mhdr, struct cmsghdr *cmsg)
{
if ((size_t) cmsg->cmsg_len < sizeof (struct cmsghdr))
/* The kernel header does this so there may be a reason. */
return NULL;
/* 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))
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)