关于getsockname的manpage和kernel行为之间不匹配

时间:2015-09-11 11:09:49

标签: linux linux-kernel

我在尝试运行fuser时遇到了一个粉碎堆栈(=缓冲区溢出)问题。我查明了iperf3调用(https://github.com/esnet/iperf/blob/master/src/net.c#L463)的原因,该调用使内核在设计的地址(getsockname())上复制的数据多于变量的大小(sizeof(sin_addr))在该地址的堆栈上。 &sa将呼叫重定向到getsockname()getname()家庭): https://github.com/torvalds/linux/blob/master/net/ipv4/af_inet.c#L698

如果我相信它的联机帮助页(ubuntu):

AF_INET
  

应初始化int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 参数以指示addrlen指向的空间量(以字节为单位)。返回时,它包含套接字地址的实际大小。

     

如果提供的缓冲区太小,则会截断返回的地址;在这种情况下,addr将返回一个大于提供给调用的值。

但在前面的代码摘录中,addrlen并不关心getname()输入值,只使用参数作为输出值。

我找到了一个链接(不能再找到它),说BSD尊重以前与Linux相反的联机帮助页摘录。

我错过了什么吗?我发现文档会有那么多的尴尬,我已经检查了其他linux addrlen调用,而且我看到的并不关心输入长度。

1 个答案:

答案 0 :(得分:1)

简短回答

我相信内核中没有检查addrlen值只是为了不浪费一些CPU周期,因为它应该始终是已知的类型(例如struct sockaddr),因此它应该始终已知并且固定大小(16字节)。所以内核只需将addrlen重写为16,无论如何。

关于你遇到的问题:我不确定为什么会这样,但实际上看起来并不是因为尺寸不匹配。我很确定内核和用户空间都具有相同的结构大小,应该传递给getsockname()系统调用(证明如下)。所以基本上你在这里描述的情况是:

  

...使内核在设计地址(& sa)复制更多数据(sizeof(sin_addr)),而不是该地址堆栈上变量的大小

并非如此。我只能想象如果确实有多少应用程序会失败。

详细说明

用户空间

iperf来源中,您有sockaddr struct(/usr/include/bits/socket.h)的下一个定义:

/* Structure describing a generic socket address.  */
struct sockaddr
  {
    __SOCKADDR_COMMON (sa_);    /* Common data: address family and length.  */
    char sa_data[14];           /* Address data.  */
  };

__SOCKADDR_COMMON宏定义如下(/usr/include/bits/sockaddr.h):

/* This macro is used to declare the initial common members
   of the data types used for socket addresses, `struct sockaddr',
   `struct sockaddr_in', `struct sockaddr_un', etc.  */

#define __SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family

sa_family_t定义为:

/* POSIX.1g specifies this type name for the `sa_family' member.  */
typedef unsigned short int sa_family_t;

所以基本上sizeof(struct sockaddr)始终是16个字节(= sizeof(char[14]) + sizeof(short))。

内核端

inet_getname()函数中,您会看到addrlen param被下一个值重写:

*uaddr_len = sizeof(*sin);

其中sin是:

DECLARE_SOCKADDR(struct sockaddr_in *, sin, uaddr);

因此,您发现sin的类型为struct sockaddr_in *。该结构定义如下(include/uapi/linux/in.h):

/* Structure describing an Internet (IP) socket address. */
#define __SOCK_SIZE__     16             /* sizeof(struct sockaddr)    */
struct sockaddr_in {
    __kernel_sa_family_t  sin_family;    /* Address family             */
    __be16                sin_port;      /* Port number                */
    struct in_addr        sin_addr;      /* Internet address           */

    /* Pad to size of `struct sockaddr'. */
    unsigned char         __pad[__SOCK_SIZE__ - sizeof(short int) -
              sizeof(unsigned short int) - sizeof(struct in_addr)];
};

所以sin变量也是16个字节长。

更新

我会尝试回复你的评论:

  

如果getsockname想要分配一个ipv6,那可能就是它溢出缓冲区的原因

getsockname()套接字调用AF_INET6时,内核会在(getsockname()系统调用中按sockfd_lookup_light()函数)计算inet6_getname()应该调用来处理你的uaddr_len请求。在这种情况下,struct sockaddr_in6 *sin = (struct sockaddr_in6 *)uaddr; ... *uaddr_len = sizeof(*sin); 将被分配下一个值:

sockaddr_in6

因此,如果您在用户空间程序中也使用sockaddr结构,那么大小将是相同的。当然,如果您的用户空间应用程序将getsockname结构传递给AF_INET6 sizeof(struct sockaddr_in6)套接字,则会出现某种溢出(因为sizeof(struct sockaddr)> iperf3 )。但我相信你所使用的iperf工具并非如此。如果是 - 它是{{1}}应该首先修复,而不是内核。