我在尝试运行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
调用,而且我看到的并不关心输入长度。
答案 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}}应该首先修复,而不是内核。