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