Linux TCP recv()和MSG_TRUNC-写入缓冲区?
我刚刚在TCP套接字上尝试在recv中使用标志MSG_TRUNC时遇到了一个意外的缓冲区溢出 而且这似乎只发生在gcc(而不是clang)中,并且只发生在使用优化进行编译时 根据这个链接: 自版本2.4以来,Linux支持在recv(2)(和recvmsg(2))的flags参数中使用MSG_TRUNC。此标志导致丢弃接收到的数据字节,而不是在调用方提供的缓冲区中传回。自Linux2.4.4以来,MSG_PEEK与MSG_OOB一起用于接收带外数据时也具有这种效果 这是否意味着不会写入提供的缓冲区?我原以为会这样,但很惊讶。 如果传递的缓冲区(非零指针)的大小大于缓冲区大小,则当客户端发送大于缓冲区的内容时,会导致缓冲区溢出。如果消息很小并且适合缓冲区(没有溢出),它实际上似乎不会将消息写入缓冲区。 显然,如果传递空指针,问题就会消失 客户端是一个简单的netcat,它发送的消息大于4个字符 服务器代码基于: 用MSG_TRUNC将read更改为recv,并将缓冲区大小更改为4(bzero也更改为4) 在Ubuntu 14.04上编译。这些编译工作正常(无警告): gcc-oserver.xserver.c clang-oserver.xserver.c clang-O2 server.x server.c 这是错误(?)编译,它还提供了一个关于问题的警告提示: gcc-O2-o server.x server.c 不管怎样,就像我提到的将指针更改为null可以解决问题,但是这是一个已知的问题吗?还是我在手册页上遗漏了什么 更新: gcc-O1也会发生缓冲区溢出。 以下是编辑警告: 在函数“recv”中, 从服务器上的“main”内联。c:47:14: /usr/include/x86_64-linux-gnu/bits/socket2.h:42:2:警告:调用“uu recv_chk_warn”并声明属性warning:recv调用的长度大于目标缓冲区的大小[默认启用] 返回警告(fd、buf、n、bos0、标志) 以下是缓冲区溢出: /server.x 10003 *检测到缓冲区溢出*:./server.x已终止 =========回溯:========= /lib/x86_64-linux-gnu/libc.so.6(+0x7338f)[0x7fcbdc44b38f] /lib/x86_64-linux-gnu/libc.so.6(uu-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-oserver.x server.c 下面是完整的代码:Linux TCP recv()和MSG_TRUNC-写入缓冲区?,c,linux,tcp,overflow,recv,C,Linux,Tcp,Overflow,Recv,我刚刚在TCP套接字上尝试在recv中使用标志MSG_TRUNC时遇到了一个意外的缓冲区溢出 而且这似乎只发生在gcc(而不是clang)中,并且只发生在使用优化进行编译时 根据这个链接: 自版本2.4以来,Linux支持在recv(2)(和recvmsg(2))的flags参数中使用MSG_TRUNC。此标志导致丢弃接收到的数据字节,而不是在调用方提供的缓冲区中传回。自Linux2.4.4以来,MSG_PEEK与MSG_OOB一起用于接收带外数据时也具有这种效果 这是否意味着不会写入提供的缓冲
/* 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;
}
/*internet域中使用TCP的简单服务器
端口号作为参数传递*/
#包括
#包括
#包括
#包括
#包括
#包括
#包括
无效错误(常量字符*消息)
{
佩罗尔(味精);
出口(1);
}
int main(int argc,char*argv[])
{
int sockfd、newsockfd、端口号;
socklen_t clilen;
字符缓冲区[4];
服务地址中的结构sockaddr\u,cli\u addr;
int n;
如果(argc<2){
fprintf(stderr,“错误,未提供端口\n”);
出口(1);
}
sockfd=套接字(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
错误(“打开套接字时出错”);
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(端口号);
如果(绑定(sockfd,(结构sockaddr*)和服务地址,
sizeof(服务地址))<0)
错误(“绑定错误”);
听(sockfd,5);
clilen=sizeof(cli_addr);
newsockfd=接受(sockfd,
(结构sockaddr*)和cli_addr,
&克莱伦);
if(newsockfd<0)
错误(“接受错误”);
b0(缓冲器,4);
n=recv(newsockfd,buffer,255,MSG_TRUNC);
如果(n<0)错误(“从套接字读取错误”);
printf(“这是消息:%s\n”,缓冲区);
n=写(newsockfd,“我收到你的消息”,18);
if(n<0)错误(“写入套接字错误”);
关闭(newsockfd);
关闭(sockfd);
返回0;
}
更新:
同样发生在Ubuntu 16.04上,gcc版本:
gcc(Ubuntu 5.4.0-6ubuntu1~16.04.2)5.4.0 20160609
我想你误解了 对于数据报套接字,
MSG_TRUNC
选项的行为如手册页中所述(至少提供最准确和最新的信息)
对于TCP套接字,手册页中的解释有点措词不当。我认为这不是一个丢弃标志,而是一个截断(或“丢弃其余部分”)操作。但是,实现(特别是Linux内核中的函数处理TCP/IPv4和TCP/IPv6套接字的详细信息)表明情况并非如此
还有一个单独的MSG\u TRUNC
socket标志。它们存储在与套接字关联的错误队列中,可以使用读取。它表示读取的数据报比缓冲区长,因此部分数据报丢失(被截断)。这很少使用,因为它实际上只与数据报套接字相关,并且有更简单的方法来确定超长数据报
数据报套接字: 对于数据报套接字,消息是分开的
nbytes = recv(socketfd, buffer, buffersize, MSG_TRUNC);
nbytes = recv(socketfd, buffer, buffersize, MSG_TRUNC);
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
ssize_t real_recv(int fd, void *buf, size_t n, int flags)
{
long retval = syscall(SYS_recvfrom, fd, buf, n, flags, NULL, NULL);
if (retval < 0) {
errno = -retval;
return -1;
} else
return (ssize_t)retval;
}