linux - 关于 getsockname 的联机帮助页和内核行为不匹配

标签 linux linux-kernel

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

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

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

The addrlen argument should be initialized to indicate the amount of space (in bytes) pointed to by addr. On return it contains the actual size of the socket address.

The returned address is truncated if the buffer provided is too small; in this case, addrlen will return a value greater than was supplied to the call.

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

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

我错过了什么吗?我发现文档如此之多很尴尬,我检查了其他 linux XXX_getname 调用,但我所看到的并不关心输入长度。

最佳答案

简答

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

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

...that makes the kernel copy more data (sizeof(sin_addr)) at the designed address (&sa) than the size of the variable on the stack at that address

不是这样的。如果这是真的,我只能想象有多少应用程序会失败。

详细解释

用户空间端

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 字节长。

更新

我会尽量回复你的评论:

if getsockname wants to allocate an ipv6 instead that may be why it overflows the buffer

当为 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 结构传递给 AF_INET6 套接字的 getsockname,将会出现某种溢出(因为 sizeof(struct sockaddr_in6) > sizeof(struct sockaddr)).但我相信您使用的 iperf3 工具并非如此。如果是——首先应该修复的是 iperf,而不是内核。

关于linux - 关于 getsockname 的联机帮助页和内核行为不匹配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32522031/

相关文章:

linux - Linux下Android Studio的插件文件夹

Python Setuptools 构建 RPM 错误

linux - 搜索函数调用 submit_bio

linux - 注册新网络设备的正确方法是什么?

linux - 我如何在 ls 命令后搜索特定字符串

linux - 页面上显示大量文件的最佳目录结构

c++ - 在 CLion 中编译简单程序时出错

linux - 如何在内核模块中访问/dev/shm 下由用户空间进程创建的 tmpfs 文件?

c - 来自 Linux 内核模块的暂停指令不起作用

linux - 如何限制操作系统中的中断次数?