add attribute nonstring

Message ID 1510594506.5755.119.camel@cavium.com
State New, archived
Headers

Commit Message

Steve Ellcey Nov. 13, 2017, 5:35 p.m. UTC
  On Sun, 2017-11-12 at 16:49 -0700, Martin Sebor wrote:
> 
> PS I still don't see it discussed on the Linux man page but
> I did find such a requirement on an AIX 6.1 ioctl man page:
> https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_61/apis/ioct
> l.htm
> 
> The descriptions of the if_indextoname and if_nametoindex
> functions specified by RFC 3493 also talk about the name being
> a nul-terminated string so it looks to me like you are correct
> and the warning has found a Glibc bug.  Yay! :)

I think this is a bug and that if_nametoindex should check for a name
that is too long.  Based on RFC 3493 it would appear that we don't need
to set errno in this case though I am not sure if that is a correct
interpretation.  I tested this patch:


2017-11-13  Steve Ellcey  <sellcey@cavium.com>

	* sysdeps/unix/sysv/linux/if_index.c (__if_nametoindex):
	Check if ifname is too long.




And it compiled fine using the latest GCC.  Apparently GCC's constant
propogation allowed it to see that sizeof could not longer be IFNAMSIZ.

Steve Ellcey
  

Comments

Joseph Myers Nov. 14, 2017, 4:36 p.m. UTC | #1
On Mon, 13 Nov 2017, Steve Ellcey wrote:

> On Sun, 2017-11-12 at 16:49 -0700, Martin Sebor wrote:
> > 
> > PS I still don't see it discussed on the Linux man page but
> > I did find such a requirement on an AIX 6.1 ioctl man page:
> > https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_61/apis/ioct
> > l.htm
> > 
> > The descriptions of the if_indextoname and if_nametoindex
> > functions specified by RFC 3493 also talk about the name being
> > a nul-terminated string so it looks to me like you are correct
> > and the warning has found a Glibc bug.  Yay! :)
> 
> I think this is a bug and that if_nametoindex should check for a name
> that is too long.  Based on RFC 3493 it would appear that we don't need
> to set errno in this case though I am not sure if that is a correct
> interpretation.  I tested this patch:
> 
> 
> 2017-11-13  Steve Ellcey  <sellcey@cavium.com>
> 
> 	* sysdeps/unix/sysv/linux/if_index.c (__if_nametoindex):
> 	Check if ifname is too long.

Florian, any comments on the proper handling of too-long names here, and 
so on how we should avoid the strncpy warnings in this case?
  
Florian Weimer Nov. 14, 2017, 4:49 p.m. UTC | #2
On 11/13/2017 06:35 PM, Steve Ellcey wrote:
> On Sun, 2017-11-12 at 16:49 -0700, Martin Sebor wrote:
>>
>> PS I still don't see it discussed on the Linux man page but
>> I did find such a requirement on an AIX 6.1 ioctl man page:
>> https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_61/apis/ioct
>> l.htm
>>
>> The descriptions of the if_indextoname and if_nametoindex
>> functions specified by RFC 3493 also talk about the name being
>> a nul-terminated string so it looks to me like you are correct
>> and the warning has found a Glibc bug.  Yay! :)
> 
> I think this is a bug and that if_nametoindex should check for a name
> that is too long.  Based on RFC 3493 it would appear that we don't need
> to set errno in this case though I am not sure if that is a correct
> interpretation.  I tested this patch:
> 
> 
> 2017-11-13  Steve Ellcey  <sellcey@cavium.com>
> 
> 	* sysdeps/unix/sysv/linux/if_index.c (__if_nametoindex):
> 	Check if ifname is too long.
> 
> 
> diff --git a/sysdeps/unix/sysv/linux/if_index.c
> b/sysdeps/unix/sysv/linux/if_index.c
> index 56f3f13..1e081c0 100644
> --- a/sysdeps/unix/sysv/linux/if_index.c
> +++ b/sysdeps/unix/sysv/linux/if_index.c
> @@ -43,6 +43,9 @@ __if_nametoindex (const char *ifname)
>     if (fd < 0)
>       return 0;
>   
> +  if (strlen (ifname) >= IFNAMSIZ)
> +    return 0;
> +
>     strncpy (ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
>     if (__ioctl (fd, SIOCGIFINDEX, &ifr) < 0)
>       {

The Linux kernel has this in include/linux/netdevice.h:

| struct net_device {
|         char                    name[IFNAMSIZ];

If I traced this correctly, the ioctl compares the passed-in name using 
strncmp:

| struct net_device *dev_get_by_name_rcu(struct net *net, const char *name)
| {
|         struct net_device *dev;
|         struct hlist_head *head = dev_name_hash(net, name);
|
|         hlist_for_each_entry_rcu(dev, head, name_hlist)
|                 if (!strncmp(dev->name, name, IFNAMSIZ))
|                         return dev;
|
|         return NULL;
| }

So this means that we should add the nostring attribute and not the 
length check.

I'll ask one of our networking people to double-check this.

Thanks,
Florian
  
Joseph Myers Nov. 14, 2017, 5:06 p.m. UTC | #3
On Tue, 14 Nov 2017, Florian Weimer wrote:

> So this means that we should add the nostring attribute and not the length
> check.

If the name passed is actually longer than this field, is it undefined 
behavior or not?  If it's not undefined behavior, I think we should avoid 
silent truncation (which means a length check, even if the check allows 
the possibility of a non-NUL-terminated value ending up in the field, 
unless for some reason truncation is always OK and does not affect the 
semantics).
  
Andreas Schwab Nov. 14, 2017, 5:55 p.m. UTC | #4
On Nov 14 2017, Joseph Myers <joseph@codesourcery.com> wrote:

> On Tue, 14 Nov 2017, Florian Weimer wrote:
>
>> So this means that we should add the nostring attribute and not the length
>> check.
>
> If the name passed is actually longer than this field, is it undefined 
> behavior or not?

The kernel will always zero-terminate the string at IFNAMSIZ-1, see
dev_ifname in net/core/dev_ioctl.c.

Andreas.
  
Florian Weimer Nov. 14, 2017, 6:21 p.m. UTC | #5
On 11/14/2017 06:55 PM, Andreas Schwab wrote:
> On Nov 14 2017, Joseph Myers <joseph@codesourcery.com> wrote:
> 
>> On Tue, 14 Nov 2017, Florian Weimer wrote:
>>
>>> So this means that we should add the nostring attribute and not the length
>>> check.
>>
>> If the name passed is actually longer than this field, is it undefined
>> behavior or not?
> 
> The kernel will always zero-terminate the string at IFNAMSIZ-1, see
> dev_ifname in net/core/dev_ioctl.c.

Yes, Hannes and I went over the code and reached the same conclusion 
(for the ioctl interface, netlink is a bit more involved to check).

However, the original patch should really use strnlen or memchr, and not 
strlen.  As posted, the strlen is either invalid because the array is 
not NUL-terminated, or it passes because the string is short enough.

Thanks,
Florian
  
Joseph Myers Nov. 14, 2017, 6:27 p.m. UTC | #6
On Tue, 14 Nov 2017, Florian Weimer wrote:

> However, the original patch should really use strnlen or memchr, and not
> strlen.  As posted, the strlen is either invalid because the array is not
> NUL-terminated, or it passes because the string is short enough.

if_nametoindex takes a char * argument.  POSIX doesn't say explicitly, but 
I'd presume that must be a NUL-terminated string, with undefined behavior 
otherwise.  That's separate from what the kernel interface is.
  
Florian Weimer Nov. 14, 2017, 6:29 p.m. UTC | #7
On 11/14/2017 07:27 PM, Joseph Myers wrote:
> On Tue, 14 Nov 2017, Florian Weimer wrote:
> 
>> However, the original patch should really use strnlen or memchr, and not
>> strlen.  As posted, the strlen is either invalid because the array is not
>> NUL-terminated, or it passes because the string is short enough.
> 
> if_nametoindex takes a char * argument.  POSIX doesn't say explicitly, but
> I'd presume that must be a NUL-terminated string, with undefined behavior
> otherwise.  That's separate from what the kernel interface is.

Oh, you are of course right.  strlen is fine under these circumstances.

So the only thing that's missing is the __set_errno (ENODEV); call, I 
think.  (It's what the ioctl should fail with for an unknown interface 
name.)

Thanks,
Florian
  

Patch

diff --git a/sysdeps/unix/sysv/linux/if_index.c
b/sysdeps/unix/sysv/linux/if_index.c
index 56f3f13..1e081c0 100644
--- a/sysdeps/unix/sysv/linux/if_index.c
+++ b/sysdeps/unix/sysv/linux/if_index.c
@@ -43,6 +43,9 @@  __if_nametoindex (const char *ifname)
   if (fd < 0)
     return 0;
 
+  if (strlen (ifname) >= IFNAMSIZ)
+    return 0;
+
   strncpy (ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
   if (__ioctl (fd, SIOCGIFINDEX, &ifr) < 0)
     {