关于 getsockname 的联机帮助页和内核行为不匹配
Mismatch between manpage and kernel behavior about getsockname
我最近在尝试 运行 iperf3
时遇到了堆栈崩溃(= 缓冲区溢出)问题。我查明了 getsockname()
调用 (https://github.com/esnet/iperf/blob/master/src/net.c#L463) 的原因,它使内核在设计地址 (&sa
) 复制比变量大小更多的数据 (sizeof(sin_addr)
)在该地址的堆栈上。
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
输入值,仅将参数用作输出值。
我发现 link(再也找不到了)说 BSD 尊重以前的联机帮助页摘录,这与 linux.
相反
我错过了什么吗?我发现文档会差那么多很尴尬,我检查了其他 linux XXX_getname
调用,但我所看到的并不关心输入长度。
简答
我相信内核中没有检查 addrlen
值只是为了不浪费一些 CPU 周期,因为它应该总是已知类型(例如 struct sockaddr
),因此它应该始终具有已知的固定大小(即 16 字节)。所以内核只是将 addrlen
重写为 16,无论如何。
关于您遇到的问题:我不确定为什么会发生这种情况,但实际上这似乎与尺寸不匹配有关。我很确定内核和用户 space 都具有相同大小的结构,应该传递给 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
不是这样的。我只能想象如果这是真的会有多少应用程序失败。
详细解释
用户space方
在 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() syscall, by sockfd_lookup_light()
function) that inet6_getname()中应该调用来处理你的请求。在这种情况下,uaddr_len
将被分配下一个值:
struct sockaddr_in6 *sin = (struct sockaddr_in6 *)uaddr;
...
*uaddr_len = sizeof(*sin);
因此,如果您也在 user-space 程序中使用 sockaddr_in6
结构,则大小将相同。当然,如果您的 userspace 应用程序将 sockaddr
结构传递给 getsockname
用于 AF_INET6
套接字,将会出现某种溢出(因为 sizeof(struct sockaddr_in6)
> sizeof(struct sockaddr)
).但我相信您使用的 iperf3
工具并非如此。如果是——首先应该修复的是 iperf
,而不是内核。
我最近在尝试 运行 iperf3
时遇到了堆栈崩溃(= 缓冲区溢出)问题。我查明了 getsockname()
调用 (https://github.com/esnet/iperf/blob/master/src/net.c#L463) 的原因,它使内核在设计地址 (&sa
) 复制比变量大小更多的数据 (sizeof(sin_addr)
)在该地址的堆栈上。
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 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
输入值,仅将参数用作输出值。
我发现 link(再也找不到了)说 BSD 尊重以前的联机帮助页摘录,这与 linux.
相反我错过了什么吗?我发现文档会差那么多很尴尬,我检查了其他 linux XXX_getname
调用,但我所看到的并不关心输入长度。
简答
我相信内核中没有检查 addrlen
值只是为了不浪费一些 CPU 周期,因为它应该总是已知类型(例如 struct sockaddr
),因此它应该始终具有已知的固定大小(即 16 字节)。所以内核只是将 addrlen
重写为 16,无论如何。
关于您遇到的问题:我不确定为什么会发生这种情况,但实际上这似乎与尺寸不匹配有关。我很确定内核和用户 space 都具有相同大小的结构,应该传递给 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
不是这样的。我只能想象如果这是真的会有多少应用程序失败。
详细解释
用户space方
在 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() syscall, by sockfd_lookup_light()
function) that inet6_getname()中应该调用来处理你的请求。在这种情况下,uaddr_len
将被分配下一个值:
struct sockaddr_in6 *sin = (struct sockaddr_in6 *)uaddr;
...
*uaddr_len = sizeof(*sin);
因此,如果您也在 user-space 程序中使用 sockaddr_in6
结构,则大小将相同。当然,如果您的 userspace 应用程序将 sockaddr
结构传递给 getsockname
用于 AF_INET6
套接字,将会出现某种溢出(因为 sizeof(struct sockaddr_in6)
> sizeof(struct sockaddr)
).但我相信您使用的 iperf3
工具并非如此。如果是——首先应该修复的是 iperf
,而不是内核。