【发布时间】:2017-01-05 23:14:37
【问题描述】:
我刚刚在 TCP 套接字上尝试在 recv 中使用标志 MSG_TRUNC 时遇到了令人惊讶的缓冲区溢出。
而且它似乎只发生在 gcc(而不是 clang)并且仅在使用优化编译时发生。
根据此链接:http://man7.org/linux/man-pages/man7/tcp.7.html
从 2.4 版开始,Linux 支持在 recv(2)(和 recvmsg(2))的 flags 参数中使用 MSG_TRUNC。此标志导致接收到的数据字节被丢弃,而不是在调用者提供的缓冲区中传回。从 Linux 2.4.4 开始,MSG_PEEK 与 MSG_OOB 结合使用时也具有此效果,以接收带外数据。
这是否意味着提供的缓冲区不会被写入?我是这么想的,但很惊讶。 如果您传递一个缓冲区(非零指针)并且大小大于缓冲区大小,则当客户端发送大于缓冲区的内容时会导致缓冲区溢出。如果消息很小并且适合缓冲区(没有溢出),它实际上似乎不会将消息写入缓冲区。 显然,如果您传递一个空指针,问题就会消失。
客户端是一个简单的 netcat,发送大于 4 个字符的消息。
服务器代码基于: http://www.linuxhowtos.org/data/6/server.c
使用 MSG_TRUNC 将读取更改为 recv,并将缓冲区大小更改为 4(bzero 也更改为 4)。
在 Ubuntu 14.04 上编译。这些编译工作正常(没有警告):
gcc -o server.x server.c
clang -o server.x server.c
clang -O2 server.x server.c
这是错误 (?) 编译,它还给出了有关问题的警告提示:
gcc -O2 -o server.x server.c
无论如何,就像我提到的将指针更改为 null 可以解决问题,但这是一个已知问题吗?还是我错过了手册页中的某些内容?
更新:
缓冲区溢出也发生在 gcc -O1 中。 这是编译警告:
在函数“recv”中, 从 server.c:47:14 的“main”内联: /usr/include/x86_64-linux-gnu/bits/socket2.h:42:2:警告:调用带有属性警告声明的“__recv_chk_warn”:recv 调用的长度大于目标缓冲区的大小[默认启用] return __recv_chk_warn(__fd, __buf, __n, __bos0 (__buf), __flags);
这里是缓冲区溢出:
./server.x 10003 * 检测到缓冲区溢出 *: ./server.x 终止 ======= 回溯:========= /lib/x86_64-linux-gnu/libc.so.6(+0x7338f)[0x7fcbdc44b38f] /lib/x86_64-linux-gnu/libc.so.6(__fortify_fail+0x5c)[0x7fcbdc4e2c9c] /lib/x86_64-linux-gnu/libc.so.6(+0x109b60)[0x7fcbdc4e1b60] /lib/x86_64-linux-gnu/libc.so.6(+0x10a023)[0x7fcbdc4e2023] ./server.x[0x400a6c] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5)[0x7fcbdc3f9ec5] ./server.x[0x400879] ======= 内存映射:======== 00400000-00401000 r-xp 00000000 08:01 17732 > /tmp/server.x ...更多信息在这里 中止(核心转储)
和gcc版本:
gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4
缓冲区和recv调用:
字符缓冲区[4];
n = recv(newsockfd,buffer,255,MSG_TRUNC);
这似乎解决了它:
n = recv(newsockfd,NULL,255,MSG_TRUNC);
这不会产生任何警告或错误:
gcc -Wall -Wextra -pedantic -o server.x server.c
这是完整的代码:
/* A simple server in the internet domain using TCP
The port number is passed as an argument */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
void error(const char *msg)
{
perror(msg);
exit(1);
}
int main(int argc, char *argv[])
{
int sockfd, newsockfd, portno;
socklen_t clilen;
char buffer[4];
struct sockaddr_in serv_addr, cli_addr;
int n;
if (argc < 2) {
fprintf(stderr,"ERROR, no port provided\n");
exit(1);
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening socket");
bzero((char *) &serv_addr, sizeof(serv_addr));
portno = atoi(argv[1]);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
if (bind(sockfd, (struct sockaddr *) &serv_addr,
sizeof(serv_addr)) < 0)
error("ERROR on binding");
listen(sockfd,5);
clilen = sizeof(cli_addr);
newsockfd = accept(sockfd,
(struct sockaddr *) &cli_addr,
&clilen);
if (newsockfd < 0)
error("ERROR on accept");
bzero(buffer,4);
n = recv(newsockfd,buffer,255,MSG_TRUNC);
if (n < 0) error("ERROR reading from socket");
printf("Here is the message: %s\n",buffer);
n = write(newsockfd,"I got your message",18);
if (n < 0) error("ERROR writing to socket");
close(newsockfd);
close(sockfd);
return 0;
}
更新: 也发生在 Ubuntu 16.04 上,带有 gcc 版本:
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.2) 5.4.0 20160609
【问题讨论】:
-
什么你得到警告?您是否尝试过启用更多警告(例如
-Wall -Wextra -pedantic或类似的)?并请显示您实际的recv调用以及缓冲区的定义。 -
另外,既然它显然可以工作,它可能不是内核的问题,而是编译器的问题,所以你能告诉我们你使用的是哪个版本的GCC吗?您是否尝试过使用更高版本的 GCC?早点?尝试禁用某些特定优化?用
-O1测试过?使用-O1进行测试,然后启用一个又一个特定的优化选项,直到您遇到问题(这样您就知道是哪一个问题的原因)? -
问题不太可能出在编译器上。它可能与 C 库有关(这可能是一个太细的区别),但更可能与 程序 有关。为了有意义地解决这个问题,我们需要可以重现问题的代码。如果您希望我们实际查看此类代码,请以minimal reproducible example 的形式呈现它。
-
并告诉我们您是如何确定存在缓冲区溢出的。