【问题标题】:Mismatch between manpage and kernel behavior about getsockname联机帮助页与有关 getsockname 的内核行为不匹配
【发布时间】:2015-09-11 11:09:48
【问题描述】:

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

如果我相信联机帮助页 (ubuntu),它会说:

int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);  

应该初始化addrlen 参数以指示addr 指向的空间量(以字节为单位)。返回时它包含套接字地址的实际大小。

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

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

我找到了一个链接(现在找不到了)说 BSD 尊重以前的手册页摘录与 linux 相反。

我错过了什么吗?我觉得文档会这么差,这很尴尬,我检查了其他 linux XXX_getname 调用,我看到的所有内容都不关心输入长度。

【问题讨论】:

    标签: linux linux-kernel


    【解决方案1】:

    简答

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

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

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

    并非如此。我只能想象如果它是真的有多少应用程序会失败。

    详细说明

    用户空间方面

    iperf 源中,您有 sockaddr 结构 (/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 参数被下一个值重写:

    *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 而不是这可能是它溢出缓冲区的原因

    当为AF_INET6 套接字调用getsockname() 时,内核将计算(在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,而不是内核。

    【讨论】:

    • 如果 getsockname 想要分配一个 ipv6 而不是这可能是它溢出缓冲区的原因。我也想这与 CPU 周期有关,但我想确定一下。在这种情况下,一个好的做法可能是始终使用 sockaddr_storage 调用 getsockname。我报告了这个bugzilla.kernel.org/show_bug.cgi?id=104601
    • 我在回答中添加了 UPDATE 部分,以了解您所指的ipv6 问题。
    • 正在等待谢谢。这仍然是文档中的一个错误,根据 posix 所说的,它也可能是内核中的一个 bug,但不想检查。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-19
    • 2011-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多