Linux 关于getsockname的手册页和内核行为不匹配

Linux 关于getsockname的手册页和内核行为不匹配,linux,linux-kernel,Linux,Linux Kernel,我最近在尝试运行iperf3时遇到了一个严重的堆栈(=缓冲区溢出)问题。我指出了getsockname()调用()的原因,该调用使得内核在设计的地址(&sa)复制的数据(sizeof(sin_addr))比该地址堆栈上的变量大小更多。 getsockname()将调用重定向到getname()(AF\u INETfamily): 如果我相信manpage(ubuntu),它会说: int-getsockname(int-sockfd,struct-sockaddr*addr,socklen\u

我最近在尝试运行
iperf3
时遇到了一个严重的堆栈(=缓冲区溢出)问题。我指出了
getsockname()
调用()的原因,该调用使得内核在设计的地址(
&sa
)复制的数据(
sizeof(sin_addr)
)比该地址堆栈上的变量大小更多。
getsockname()
将调用重定向到
getname()
AF\u INET
family):

如果我相信manpage(ubuntu),它会说:

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

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

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

我发现一个链接(再也找不到了)说BSD尊重上一个与linux相反的手册页摘录

我错过什么了吗?我觉得很尴尬,文档可能会有那么多内容,我检查了其他linux
XXX\u getname
调用,我看到的所有调用都不关心输入长度。

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

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

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

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

详细说明 用户空间端 在
iperf
源中,您有
sockaddr
struct(
/usr/include/bits/socket.h
)的下一个定义:

描述通用套接字地址的结构*/ 结构sockaddr { __SOCKADDR_COMMON(sa_);/*公共数据:地址族和长度*/ char sau data[14];/*地址数据*/ }; 和定义如下的宏(
/usr/include/bits/SOCKADDR.h
):

/*此宏用于声明初始公共成员
在用于套接字地址的数据类型中,`struct sockaddr',
`结构sockaddr_in','struct sockaddr_un'等*/
#定义uu SOCKADDR_公共(sa_前缀)\
萨乌族萨乌前缀萨乌族
sa_family\u t
定义为:

/*POSIX.1g为“sa_族”成员指定此类型名称*/
typedef无符号短整数系列;
所以基本上
sizeof(struct sockaddr)
总是16个字节(
sizeof(char[14])
+
sizeof(short)

内核端 在
inet_getname()
函数中,可以看到
addrlen
参数被下一个值重写:

*uaddr\u len=sizeof(*sin);
其中
sin
为:

DECLARE_SOCKADDR(结构SOCKADDR_in*,sin,uaddr);
因此您可以看到,
sin
在*中具有
struct sockaddr\u的类型。此结构定义如下(
include/uapi/linux/in.h
):

描述Internet(IP)套接字地址的结构*/ #定义_SOCK_SIZE__16/*sizeof(struct sockaddr)*/ 结构sockaddr\u in{ __kernel_sa_family_t sin_family;/*地址族*/ __be16 sin_端口号;/*端口号*/ 结构in_addr sin_addr;/*互联网地址*/ /*焊盘尺寸为'struct sockaddr'*/ 无符号字符pad[\uuuuuuuu SOCK\u SIZE\uuuuuuuuuu-sizeof(短整型)- sizeof(unsigned short int)-sizeof(struct in_addr)]; };
所以
sin
变量也是16字节长

更新 我将尝试回复您的评论:


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

当为
AF\u INET6
socket调用
getsockname()
时,内核将显示(在syscall中,通过
sockfd\u lookup\u light()
函数)应该被调用以处理您的请求。在这种情况下,
uaddr\u len
将被分配下一个值:

struct sockaddr_in6*sin=(struct sockaddr_in6*)uaddr;
...    
*uaddr_len=sizeof(*sin);

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

如果getsockname想要分配ipv6,这可能就是它溢出缓冲区的原因。我也猜这是关于CPU周期的,不过我想确定一下。在这种情况下,一个好的做法可能是始终使用sockaddr_存储调用getsockname。我补充说,我报告了这一点