From patchwork Mon May 16 21:03:22 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arjun Shankar X-Patchwork-Id: 54056 X-Patchwork-Delegate: siddhesh@gotplt.org Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 9853A38485A2 for ; Mon, 16 May 2022 21:04:11 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 9853A38485A2 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1652735051; bh=A4tOPYhlFtDcv6t28/mbKSg9I3cCKH+ffe1PfGB1T7I=; h=To:Subject:Date:List-Id:List-Unsubscribe:List-Archive:List-Post: List-Help:List-Subscribe:From:Reply-To:Cc:From; b=K5sZhe/lx9Vl24JF4tysQX+/WPhokv8wKpe/N4O1uqG6mpeb3GQ2Pv3jXDi5fC4GR AakKk3//GElL5LCZrwju9qXvqduo5Wq7ef3yVz1SFeQ5JLXFnXg21QP5beb+g7/sGH eYB172L4qFZEAI91EcjvE43AaZ82P37KDgbKsXPg= X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTPS id 45BF6385780F for ; Mon, 16 May 2022 21:03:49 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 45BF6385780F Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-491-Y205kvs6Pd2RR-R5AkfPGw-1; Mon, 16 May 2022 17:03:47 -0400 X-MC-Unique: Y205kvs6Pd2RR-R5AkfPGw-1 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.rdu2.redhat.com [10.11.54.4]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 71B64811E75 for ; Mon, 16 May 2022 21:03:47 +0000 (UTC) Received: from x1carbon.redhat.com (unknown [10.40.194.9]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 593372026D64; Mon, 16 May 2022 21:03:33 +0000 (UTC) To: libc-alpha@sourceware.org Subject: [PATCH] socket: Check lengths before advancing pointer in CMSG_NXTHDR Date: Mon, 16 May 2022 23:03:22 +0200 Message-Id: <20220516210322.2034309-1-arjun@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.78 on 10.11.54.4 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-11.2 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_LOW, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE, URIBL_BLACK autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Arjun Shankar via Libc-alpha From: Arjun Shankar Reply-To: Arjun Shankar Cc: Florian Weimer Errors-To: libc-alpha-bounces+patchwork=sourceware.org@sourceware.org Sender: "Libc-alpha" 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. --- bits/socket.h | 21 ++++--- socket/Makefile | 1 + socket/tst-cmsghdr-skeleton.c | 83 +++++++++++++++++++++++++++ socket/tst-cmsghdr.c | 56 ++++++++++++++++++ sysdeps/mach/hurd/bits/socket.h | 21 ++++--- sysdeps/unix/sysv/linux/bits/socket.h | 21 ++++--- sysdeps/unix/sysv/linux/cmsg_nxthdr.c | 23 +++++--- 7 files changed, 197 insertions(+), 29 deletions(-) create mode 100644 socket/tst-cmsghdr-skeleton.c create mode 100644 socket/tst-cmsghdr.c diff --git a/bits/socket.h b/bits/socket.h index 2b99dea33b..b0a662b241 100644 --- a/bits/socket.h +++ b/bits/socket.h @@ -254,18 +254,25 @@ extern struct cmsghdr *__cmsg_nxthdr (struct msghdr *__mhdr, _EXTERN_INLINE struct cmsghdr * __NTH (__cmsg_nxthdr (struct msghdr *__mhdr, struct cmsghdr *__cmsg)) { + /* 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. */ + + /* 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 (CMSG_ALIGN (__cmsg->cmsg_len) + sizeof (struct cmsghdr) + > (size_t) + (((unsigned char *) __mhdr->msg_control + __mhdr->msg_controllen) + - (unsigned char *) __cmsg)) + + 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/socket/Makefile b/socket/Makefile index 156eec6c85..2bde78387f 100644 --- a/socket/Makefile +++ b/socket/Makefile @@ -34,6 +34,7 @@ routines := accept bind connect getpeername getsockname getsockopt \ tests := \ tst-accept4 \ tst-sockopt \ + tst-cmsghdr \ # tests tests-internal := \ diff --git a/socket/tst-cmsghdr-skeleton.c b/socket/tst-cmsghdr-skeleton.c new file mode 100644 index 0000000000..cb3725f221 --- /dev/null +++ b/socket/tst-cmsghdr-skeleton.c @@ -0,0 +1,83 @@ +/* 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 + . */ + +/* 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. */ + +#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 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; +} diff --git a/socket/tst-cmsghdr.c b/socket/tst-cmsghdr.c new file mode 100644 index 0000000000..68c96d3c9d --- /dev/null +++ b/socket/tst-cmsghdr.c @@ -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 + . */ + +#include +#include +#include +#include + +#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 diff --git a/sysdeps/mach/hurd/bits/socket.h b/sysdeps/mach/hurd/bits/socket.h index 5b35ea81ec..4aabff3c29 100644 --- a/sysdeps/mach/hurd/bits/socket.h +++ b/sysdeps/mach/hurd/bits/socket.h @@ -258,18 +258,25 @@ extern struct cmsghdr *__cmsg_nxthdr (struct msghdr *__mhdr, _EXTERN_INLINE struct cmsghdr * __NTH (__cmsg_nxthdr (struct msghdr *__mhdr, struct cmsghdr *__cmsg)) { + /* 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. */ + + /* 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 (CMSG_ALIGN (__cmsg->cmsg_len) + sizeof (struct cmsghdr) + > (size_t) + (((unsigned char *) __mhdr->msg_control + __mhdr->msg_controllen) + - (unsigned char *) __cmsg)) + + 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..5b9d875e31 100644 --- a/sysdeps/unix/sysv/linux/bits/socket.h +++ b/sysdeps/unix/sysv/linux/bits/socket.h @@ -315,18 +315,25 @@ extern struct cmsghdr *__cmsg_nxthdr (struct msghdr *__mhdr, _EXTERN_INLINE struct cmsghdr * __NTH (__cmsg_nxthdr (struct msghdr *__mhdr, struct cmsghdr *__cmsg)) { + /* 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. */ + + /* 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 (CMSG_ALIGN (__cmsg->cmsg_len) + sizeof (struct cmsghdr) + > (size_t) + (((unsigned char *) __mhdr->msg_control + __mhdr->msg_controllen) + - (unsigned char *) __cmsg)) + + 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..60c1fa65c0 100644 --- a/sysdeps/unix/sysv/linux/cmsg_nxthdr.c +++ b/sysdeps/unix/sysv/linux/cmsg_nxthdr.c @@ -23,18 +23,25 @@ struct cmsghdr * __cmsg_nxthdr (struct msghdr *mhdr, struct cmsghdr *cmsg) { + /* 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. */ + + /* 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 (CMSG_ALIGN (cmsg->cmsg_len) + sizeof (struct cmsghdr) + > (size_t) + (((unsigned char *) mhdr->msg_control + mhdr->msg_controllen) + - (unsigned char *) cmsg)) + + 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)