Message ID | f6a8c32f-f524-9ebb-03bc-4484f8a80a16@gmail.com |
---|---|
State | New |
Headers | show |
Series | avoid buffer overflow in sunrpc clnt_create (BZ #22542) | expand |
> struct sockaddr_un > { > __SOCKADDR_COMMON (sun_); > - char sun_path[108]; /* Path name. */ > + char sun_path[108] > + __attribute_nonstring__; /* Path name. */ > }; This says "sun_path uses strncpy format", but.... > + if (strlen (hostname) >= sizeof sun.sun_path) > + { > + struct rpc_createerr *ce = &get_rpc_createerr (); > + ce->cf_stat = RPC_UNKNOWNHOST; > + ce->cf_error.re_errno = EINVAL; > + return NULL; > + } ... this says "sun_path uses ordinary string format", which isn't consistent. I suggest that sun_path should use ordinary string format, since that's what people expect. In other words, do not add __attribute_nonstring__ or change clntunix_create, but instead just add the strlen check to clnt_create. You might also consider using "__strnlen (hostname, sizeof sun.sun_path)" instead of "strlen (hostname)" to avoid bad asymptotic behavior if HOSTNAME is long.
On Sun, Dec 03, 2017 at 05:36:50PM -0800, Paul Eggert wrote: [...] > I suggest that sun_path should use ordinary string format, since that's what > people expect. Do people really expect that? Assuming that people are aware of linux kernel behaviour, why would they expect that?
Dmitry V. Levin wrote: > Do people really expect that? Assuming that people are aware > of linux kernel behaviour, why would they expect that? These days, it's because strncpy format is obsolete and is not something programmers are ordinarily aware of. When in doubt (which there seems to be here), glibc should use null-terminated strings rather than strncpy format.
On Mon, Dec 04, 2017 at 12:04:12AM -0800, Paul Eggert wrote: > Dmitry V. Levin wrote: > > Do people really expect that? Assuming that people are aware > > of linux kernel behaviour, why would they expect that? > > These days, it's because strncpy format is obsolete and is not something > programmers are ordinarily aware of. When in doubt (which there seems to be > here), glibc should use null-terminated strings rather than strncpy format. Is there any statistics what programmers are ordinarily aware of? I have no doubts that some valid code[1] no longer compiles with -Werror=stringop-truncation, and the only plausible fix is to mark struct sockaddr_un.sun_path with __attribute_nonstring__. I think we should revisit the patch submitted by Martin. [1] strace HEAD's tests no longer build in Fedora Rawhide with the following diagnostics: net-accept-connect.c: In function ‘main’: net-accept-connect.c:57:2: error: ‘strncpy’ specified bound 108 equals destination size [-Werror=stringop-truncation] strncpy(addr.sun_path, av[1], sizeof(addr.sun_path));
On 02/06/2018 01:06 PM, Dmitry V. Levin wrote:
> On Mon, Dec 04, 2017 at 12:04:12AM -0800, Paul Eggert wrote: >> Dmitry V. Levin wrote: >>> Do people really expect that? Assuming
that people are aware >>> of linux kernel behaviour, why would they
expect that? >> >> These days, it's because strncpy format is obsolete
and is not something >> programmers are ordinarily aware of. When in
doubt (which there seems to be >> here), glibc should use
null-terminated strings rather than strncpy format. > > Is there any
statistics what programmers are ordinarily aware of? > > I have no
doubts that some valid code[1] no longer compiles with >
-Werror=stringop-truncation, and the only plausible fix is to mark >
struct sockaddr_un.sun_path with __attribute_nonstring__. If memory
serves, a problem is that some glibc-related uses of struct
sockaddr_un.sun_path assume strncpy format and others do not. If so,
marking it with __attribute_nonstring__ will pacify uses in some cases
(such as the one you mention), while masking bugs in others. If there's
some doubt about the intended format of this buffer, then it's safer for
everybody to stick to null-terminated strings in it.
For unusual programs such as the test program you mention, it's easy
enough to use memcpy instead of strncpy to test buffers that are not
null-terminated. Using memcpy would be better for this kind of test
anyway, since it would allow a test case where the null byte at the end
of the string is not followed by nulls all the way to the end of the buffer.
On Tue, Feb 06, 2018 at 04:54:45PM -0800, Paul Eggert wrote: > On 02/06/2018 01:06 PM, Dmitry V. Levin wrote: > > On Mon, Dec 04, 2017 at 12:04:12AM -0800, Paul Eggert wrote: >> Dmitry V. Levin wrote: >>> Do people really expect that? Assuming > that people are aware >>> of linux kernel behaviour, why would they > expect that? >> >> These days, it's because strncpy format is obsolete > and is not something >> programmers are ordinarily aware of. When in > doubt (which there seems to be >> here), glibc should use > null-terminated strings rather than strncpy format. > > Is there any > statistics what programmers are ordinarily aware of? > > I have no > doubts that some valid code[1] no longer compiles with > > -Werror=stringop-truncation, and the only plausible fix is to mark > > struct sockaddr_un.sun_path with __attribute_nonstring__. If memory > serves, a problem is that some glibc-related uses of struct > sockaddr_un.sun_path assume strncpy format and others do not. If so, > marking it with __attribute_nonstring__ will pacify uses in some cases > (such as the one you mention), while masking bugs in others. If there's > some doubt about the intended format of this buffer, then it's safer for > everybody to stick to null-terminated strings in it. > > For unusual programs such as the test program you mention, it's easy > enough to use memcpy instead of strncpy to test buffers that are not > null-terminated. Using memcpy would be better for this kind of test > anyway, since it would allow a test case where the null byte at the end > of the string is not followed by nulls all the way to the end of the buffer. I don't see why a valid code has to be maimed to cater for broken code. If anything, it's broken code that has to be changed, not otherwise. I hope you don't really suggest people to apply changes like this to their valid code: - strncpy(addr.sun_path, av[1], sizeof(addr.sun_path)); + memcpy(addr.sun_path, av[1], strlen(av[1]) < sizeof(addr.sun_path) ? strlen(av[1]) + 1 : sizeof(addr.sun_path));
On 02/06/2018 02:06 PM, Dmitry V. Levin wrote: > On Mon, Dec 04, 2017 at 12:04:12AM -0800, Paul Eggert wrote: >> Dmitry V. Levin wrote: >>> Do people really expect that? Assuming that people are aware >>> of linux kernel behaviour, why would they expect that? >> >> These days, it's because strncpy format is obsolete and is not something >> programmers are ordinarily aware of. When in doubt (which there seems to be >> here), glibc should use null-terminated strings rather than strncpy format. > > Is there any statistics what programmers are ordinarily aware of? > > I have no doubts that some valid code[1] no longer compiles with > -Werror=stringop-truncation, and the only plausible fix is to mark > struct sockaddr_un.sun_path with __attribute_nonstring__. > > I think we should revisit the patch submitted by Martin. > > [1] strace HEAD's tests no longer build in Fedora Rawhide with the following > diagnostics: > net-accept-connect.c: In function ‘main’: > net-accept-connect.c:57:2: error: ‘strncpy’ specified bound 108 equals destination size [-Werror=stringop-truncation] > strncpy(addr.sun_path, av[1], sizeof(addr.sun_path)); I was going to follow up on the original thread but got stuck trying to come up with a test case showing the kernel creating a path with no terminating nul, and I've been too busy with GCC work to get back to it. I'm also worried that annotating the member nonstring will lead to many more false positives for the canonical use case of computing the path length/size using strlen even on input (to the kernel/library) than true positives for the elusive cases when there is no nul on output. (Attribute nonstring causes warnings when an array or pointer declared with is passed to strlen or other functions that expect a nul- terminated string.) Martin
On 02/07/2018 02:53 AM, Dmitry V. Levin wrote: > I don't see why a valid code has to be maimed to cater for broken code. The problem here is that there is a dispute over what is "valid" and what is "broken". It's only a minor disagreement and shouldn't affect typical usage, but it is a disagreement. A good course here is to follow the usual advice about being strict about what you generate and generous about what you accept. We don't want to penalize code that is taking such an approach, in an attempt to slightly simplify code that is zealously enforcing one side of the dispute. > I hope you don't really suggest people to apply changes like this > to their valid code: No, because that particular code isn't valid and should be fixed anyway, regardless of whether one assumes sun_path uses strncpy format. The problem is that the code mistakenly assumes that 'socket' always creates a file whose name is given by av[1] and then goes off and unlinks av[1] to make room for the new file, but this is incorrect when av[1]'s length exceeds sizeof(addr.sun_path). That is, this code: assert(strlen(av[1]) > 0); strncpy(addr.sun_path, av[1], sizeof(addr.sun_path)); len = offsetof(struct sockaddr_un, sun_path) + strlen(av[1]) + 1; if (len > sizeof(addr)) len = sizeof(addr); unlink(av[1]); should be replaced by something like this code: assert(0 < strlen(av[1]) && strlen(av[1]) < sizeof(addr.sun_path)); strcpy(addr.sun_path, av[1]); len = offsetof(struct sockaddr_un, sun_path) + strlen(av[1]) + 1; unlink(av[1]); and once you do that, the problem goes away regardless of whether sun_path uses strncpy format. In short, this particular test case should be changed to be strict about what it generates, and glibc should support this sort of usage instead of introducing artificial roadblocks against it.
On Wed, Feb 07, 2018 at 11:52:56AM -0800, Paul Eggert wrote: [...] > No, because that particular code isn't valid and should be fixed anyway, > regardless of whether one assumes sun_path uses strncpy format. The > problem is that the code mistakenly assumes that 'socket' always creates > a file whose name is given by av[1] and then goes off and unlinks av[1] > to make room for the new file, but this is incorrect when av[1]'s length > exceeds sizeof(addr.sun_path). That is, this code: > > assert(strlen(av[1]) > 0); > > strncpy(addr.sun_path, av[1], sizeof(addr.sun_path)); > len = offsetof(struct sockaddr_un, sun_path) + strlen(av[1]) + 1; > if (len > sizeof(addr)) > len = sizeof(addr); > > unlink(av[1]); > > should be replaced by something like this code: > > assert(0 < strlen(av[1]) && strlen(av[1]) < sizeof(addr.sun_path)); > > strcpy(addr.sun_path, av[1]); > len = offsetof(struct sockaddr_un, sun_path) + strlen(av[1]) + 1; > > unlink(av[1]); The main purpose of this test is to check that strace decodes sun_path properly, in particular, that the last byte of non-NUL-terminated sun_path is not lost (it's a regression test for strace commit v4.9-248-gf57bd11), so strcpy is not applicable here. If strncpy starts generating a compilation error, then the only available choice seems to be memcpy: len = strlen(av[1]); assert(len > 0 && len <= sizeof(addr.sun_path)); if (++len > sizeof(addr.sun_path)) len = sizeof(addr.sun_path); memcpy(addr.sun_path, av[1], len); len += offsetof(struct sockaddr_un, sun_path); unlink(av[1]); As struct sockaddr_un.sun_path is not necessarily a C string, pretending that it is a C string would encourage users to replace strncpy with memcpy.
On Wed, Feb 07, 2018 at 08:57:46AM -0700, Martin Sebor wrote: > On 02/06/2018 02:06 PM, Dmitry V. Levin wrote: > > On Mon, Dec 04, 2017 at 12:04:12AM -0800, Paul Eggert wrote: > >> Dmitry V. Levin wrote: > >>> Do people really expect that? Assuming that people are aware > >>> of linux kernel behaviour, why would they expect that? > >> > >> These days, it's because strncpy format is obsolete and is not something > >> programmers are ordinarily aware of. When in doubt (which there seems to be > >> here), glibc should use null-terminated strings rather than strncpy format. > > > > Is there any statistics what programmers are ordinarily aware of? > > > > I have no doubts that some valid code[1] no longer compiles with > > -Werror=stringop-truncation, and the only plausible fix is to mark > > struct sockaddr_un.sun_path with __attribute_nonstring__. > > > > I think we should revisit the patch submitted by Martin. > > > > [1] strace HEAD's tests no longer build in Fedora Rawhide with the following > > diagnostics: > > net-accept-connect.c: In function ‘main’: > > net-accept-connect.c:57:2: error: ‘strncpy’ specified bound 108 equals destination size [-Werror=stringop-truncation] > > strncpy(addr.sun_path, av[1], sizeof(addr.sun_path)); > > I was going to follow up on the original thread but got stuck > trying to come up with a test case showing the kernel creating > a path with no terminating nul, and I've been too busy with > GCC work to get back to it. > > I'm also worried that annotating the member nonstring will > lead to many more false positives for the canonical use case > of computing the path length/size using strlen even on input > (to the kernel/library) than true positives for the elusive > cases when there is no nul on output. (Attribute nonstring > causes warnings when an array or pointer declared with is > passed to strlen or other functions that expect a nul- > terminated string.) struct sockaddr_un.sun_path is not a nul-terminated string, one has to use strnlen instead of strlen.
On 02/07/2018 01:25 PM, Dmitry V. Levin wrote: > If strncpy starts generating a compilation error, > then the only available choice seems to be memcpy: Yes, memcpy is typically the way to go here. > len = strlen(av[1]); > assert(len > 0 && len <= sizeof(addr.sun_path)); > > if (++len > sizeof(addr.sun_path)) > len = sizeof(addr.sun_path); > > memcpy(addr.sun_path, av[1], len); > len += offsetof(struct sockaddr_un, sun_path); > > unlink(av[1]); Yes, that should also work and it'll fix the unlink bug that I mentioned. You might also want to replace the "if" statement with "len += len < sizeof(addr.sun_path);", as that's simpler. > As struct sockaddr_un.sun_path is not necessarily a C string, pretending > that it is a C string would encourage users to replace strncpy with > memcpy. There's nothing wrong with using memcpy for this test. On the contrary, memcpy improves the test by not unnecessarily initializing the part of addr.sun_path that doesn't need initializing. Programs like valgrind can use this information to catch bugs that the strncpy version would mask.
On 02/07/2018 02:31 PM, Dmitry V. Levin wrote: > On Wed, Feb 07, 2018 at 08:57:46AM -0700, Martin Sebor wrote: >> On 02/06/2018 02:06 PM, Dmitry V. Levin wrote: >>> On Mon, Dec 04, 2017 at 12:04:12AM -0800, Paul Eggert wrote: >>>> Dmitry V. Levin wrote: >>>>> Do people really expect that? Assuming that people are aware >>>>> of linux kernel behaviour, why would they expect that? >>>> >>>> These days, it's because strncpy format is obsolete and is not something >>>> programmers are ordinarily aware of. When in doubt (which there seems to be >>>> here), glibc should use null-terminated strings rather than strncpy format. >>> >>> Is there any statistics what programmers are ordinarily aware of? >>> >>> I have no doubts that some valid code[1] no longer compiles with >>> -Werror=stringop-truncation, and the only plausible fix is to mark >>> struct sockaddr_un.sun_path with __attribute_nonstring__. >>> >>> I think we should revisit the patch submitted by Martin. >>> >>> [1] strace HEAD's tests no longer build in Fedora Rawhide with the following >>> diagnostics: >>> net-accept-connect.c: In function ‘main’: >>> net-accept-connect.c:57:2: error: ‘strncpy’ specified bound 108 equals destination size [-Werror=stringop-truncation] >>> strncpy(addr.sun_path, av[1], sizeof(addr.sun_path)); >> >> I was going to follow up on the original thread but got stuck >> trying to come up with a test case showing the kernel creating >> a path with no terminating nul, and I've been too busy with >> GCC work to get back to it. >> >> I'm also worried that annotating the member nonstring will >> lead to many more false positives for the canonical use case >> of computing the path length/size using strlen even on input >> (to the kernel/library) than true positives for the elusive >> cases when there is no nul on output. (Attribute nonstring >> causes warnings when an array or pointer declared with is >> passed to strlen or other functions that expect a nul- >> terminated string.) > > struct sockaddr_un.sun_path is not a nul-terminated string, > one has to use strnlen instead of strlen. It may not be guaranteed to be nul-terminated, but it is nul terminated most of the time (when the path is shorter than the array), otherwise there wouldn't be so much working code that depends on it and so much documentation with examples that assume it is, including Glibc's own manual and the Linux man page: http://man7.org/linux/man-pages/man7/unix.7.html Once sun_path is declared nonstring all code that follows these examples and in which GCC cannot prove the string is nul-terminated will trigger the same warning. The only difference will be that the warning will point to sun_path being passed to strlen or as the second argument to strcpy. The question on my mind is: will the true positives/bugs the warning finds outnumber the false positives by enough of a margin that the warning will be viewed as useful. To be able to even start to answer the question I'd need to see an example where the sun_path isn't nul-terminated and get a sense of how frequently that might happen in practice. Martin
* Paul Eggert: >> struct sockaddr_un >> { >> __SOCKADDR_COMMON (sun_); >> - char sun_path[108]; /* Path name. */ >> + char sun_path[108] >> + __attribute_nonstring__; /* Path name. */ >> }; > > This says "sun_path uses strncpy format", but.... > >> + if (strlen (hostname) >= sizeof sun.sun_path) >> + { >> + struct rpc_createerr *ce = &get_rpc_createerr (); >> + ce->cf_stat = RPC_UNKNOWNHOST; >> + ce->cf_error.re_errno = EINVAL; >> + return NULL; >> + } > > ... this says "sun_path uses ordinary string format", which isn't consistent. > > I suggest that sun_path should use ordinary string format, since > that's what people expect. In other words, do not add > __attribute_nonstring__ or change clntunix_create, but instead just > add the strlen check to clnt_create. There is a definition of SUN_LEN in <sys/un.h> that uses strlen, not strnlen: /* Evaluate to actual length of the `sockaddr_un' structure. */ # define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + strlen ((ptr)->sun_path)) The kernel adds its own terminator, after the user-specified struct length, so it should be fine with a pathname that is 108 bytes long. Thanks, Florian
2017-12-03 Martin Sebor <msebor@redhat.com> [BZ #22542] * socket/sys/un.h (struct sockaddr_un): Declare sun_path attribute nonstring. * sunrpc/clnt_gen.c (clnt_create): Avoid buffer overflow. * sunrpc/clnt_unix.c (clntunix_create): Use strnlen instead of strlen. * sunrpc/tst-unix-name-too-long.c: New test. * sunrpc/Makefile (tests): Add tst-unix-name-too-long. diff --git a/socket/sys/un.h b/socket/sys/un.h index fc82f8c..f4e0715 100644 --- a/socket/sys/un.h +++ b/socket/sys/un.h @@ -29,7 +29,8 @@ __BEGIN_DECLS struct sockaddr_un { __SOCKADDR_COMMON (sun_); - char sun_path[108]; /* Path name. */ + char sun_path[108] + __attribute_nonstring__; /* Path name. */ }; diff --git a/sunrpc/Makefile b/sunrpc/Makefile index f1b8323..431634c 100644 --- a/sunrpc/Makefile +++ b/sunrpc/Makefile @@ -95,7 +95,7 @@ others += rpcgen endif tests = tst-xdrmem tst-xdrmem2 test-rpcent tst-udp-error tst-udp-timeout \ - tst-udp-nonblocking + tst-udp-nonblocking tst-unix-name-too-long xtests := tst-getmyaddr ifeq ($(have-thread-library),yes) @@ -166,6 +166,7 @@ $(objpfx)tst-xdrmem2: $(common-objpfx)linkobj/libc.so $(objpfx)tst-udp-error: $(common-objpfx)linkobj/libc.so $(objpfx)tst-svc_register: \ $(common-objpfx)linkobj/libc.so $(shared-thread-library) +$(objpfx)tst-unix-name-too-long: $(common-objpfx)linkobj/libc.so $(objpfx)rpcgen: $(addprefix $(objpfx),$(rpcgen-objs)) @@ -250,3 +251,4 @@ $(objpfx)tst-udp-timeout: $(common-objpfx)linkobj/libc.so $(objpfx)tst-udp-nonblocking: $(common-objpfx)linkobj/libc.so $(objpfx)tst-udp-garbage: \ $(common-objpfx)linkobj/libc.so $(shared-thread-library) +$(objpfx)/tst-unix-name-too-long: $(common-objpfx)linkobj/libc.so diff --git a/sunrpc/clnt_gen.c b/sunrpc/clnt_gen.c index 13ced89..8d354ec 100644 --- a/sunrpc/clnt_gen.c +++ b/sunrpc/clnt_gen.c @@ -59,6 +59,14 @@ clnt_create (const char *hostname, u_long prog, u_long vers, { memset ((char *)&sun, 0, sizeof (sun)); sun.sun_family = AF_UNIX; + if (strlen (hostname) >= sizeof sun.sun_path) + { + struct rpc_createerr *ce = &get_rpc_createerr (); + ce->cf_stat = RPC_UNKNOWNHOST; + ce->cf_error.re_errno = EINVAL; + return NULL; + } + strcpy (sun.sun_path, hostname); sock = RPC_ANYSOCK; client = clntunix_create (&sun, prog, vers, &sock, 0, 0); diff --git a/sunrpc/clnt_unix.c b/sunrpc/clnt_unix.c index 33a02cc..9bb24c5 100644 --- a/sunrpc/clnt_unix.c +++ b/sunrpc/clnt_unix.c @@ -134,7 +134,8 @@ clntunix_create (struct sockaddr_un *raddr, u_long prog, u_long vers, if (*sockp < 0) { *sockp = __socket (AF_UNIX, SOCK_STREAM, 0); - len = strlen (raddr->sun_path) + sizeof (raddr->sun_family) + 1; + len = (__strnlen (raddr->sun_path, sizeof (raddr->sun_path)) + + sizeof (raddr->sun_family) + 1); if (*sockp < 0 || __connect (*sockp, (struct sockaddr *) raddr, len) < 0) { diff --git a/sunrpc/tst-unix-name-too-long.c b/sunrpc/tst-unix-name-too-long.c new file mode 100644 index 0000000..82df7ef --- /dev/null +++ b/sunrpc/tst-unix-name-too-long.c @@ -0,0 +1,44 @@ +/* Test to verify that overlong hostname is rejected by clnt_create() + and doesn't cause a buffer overflow (bug 22542). + + Copyright (C) 2017 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 + <http://www.gnu.org/licenses/>. */ + +#include <errno.h> +#include <rpc/clnt.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <string.h> + + +static int +do_test (void) +{ + /* Create an arbitrary hostname that's longer than fits in sun_path. */ + char name [sizeof ((struct sockaddr_un*)0)->sun_path * 2]; + memset (name, 'x', sizeof name - 1); + name [sizeof name - 1] = '\0'; + + CLIENT *clnt = clnt_create (name, 0, 0, "unix"); + + if (clnt) + clnt_destroy (clnt); + + return clnt == 0 && errno == EINVAL; +} + +#include <support/test-driver.c>