diff mbox series

avoid buffer overflow in sunrpc clnt_create (BZ #22542)

Message ID f6a8c32f-f524-9ebb-03bc-4484f8a80a16@gmail.com
State New
Headers show
Series avoid buffer overflow in sunrpc clnt_create (BZ #22542) | expand

Commit Message

Martin Sebor Dec. 3, 2017, 11:09 p.m. UTC
Annotating sockaddr_un::sun_path with attribute nonstring as
suggested by Carlos in [1] triggers a warning for call to strlen()
with the array as an argument in the clntunix_create() function.

The only caller of the function in Glibc, clnt_create(), calls
strcpy() to copy the string passed to it as the hostname argument
to the sun_path array member of a local struct sockadd_un object.
This causes a buffer overflow when the string is longer than
the size of the sun_path member array.

The attached patch tries to fix both of these problems.

It annotates the sun_path array member of struct sockaddr_un
with attribute nonstring.

It then changes clntunix_create() to use strnlen() instead of
strlen() to read as most as many characters from the sun_path
array as it has elements.

Finally, the patch changes the clnt_create() function to reject
hostnames longer than sizeof (sun_path) to avoid the overflow.

I considered changing clnt_create() to dynamically allocate
a larger object than sizeof (struct sockaddr_un) to fit the
whole hostname but decided to keep it simple.  Longer names
aren't supported now (they cause a buffer overflow) and since
no one complained, it seems good enough.  However, if it is
considered important to handle longer hostnames, it can be
changed.

Given the approach I chose in clnt_create(), using strnlen()
in clntunix_create() isn't really necessary to avoid reading
past the end of sun_path because it's nul-terminated, but it
is necessary to avoid the warning.

If it's decided that clnt_create() should handle overlong
hostnames then clntunix_create() will need to change back
to use either strlen(sun_path) and the warning will need
to be suppressed by some other means, or to use memchr().

Thanks
Martin

[1] https://sourceware.org/ml/libc-alpha/2017-11/msg00932.html

Comments

Paul Eggert Dec. 4, 2017, 1:36 a.m. UTC | #1
>  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.
Dmitry V. Levin Dec. 4, 2017, 1:55 a.m. UTC | #2
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?
Paul Eggert Dec. 4, 2017, 8:04 a.m. UTC | #3
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.
Dmitry V. Levin Feb. 6, 2018, 9:06 p.m. UTC | #4
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));
Paul Eggert Feb. 7, 2018, 12:54 a.m. UTC | #5
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.
Dmitry V. Levin Feb. 7, 2018, 10:53 a.m. UTC | #6
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));
Martin Sebor Feb. 7, 2018, 3:57 p.m. UTC | #7
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
Paul Eggert Feb. 7, 2018, 7:52 p.m. UTC | #8
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.
Dmitry V. Levin Feb. 7, 2018, 9:25 p.m. UTC | #9
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.
Dmitry V. Levin Feb. 7, 2018, 9:31 p.m. UTC | #10
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.
Paul Eggert Feb. 7, 2018, 10:37 p.m. UTC | #11
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.
Martin Sebor Feb. 7, 2018, 11:13 p.m. UTC | #12
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
Florian Weimer Jan. 12, 2022, 9:30 a.m. UTC | #13
* 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
diff mbox series

Patch

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>