Linux TCP recv()和MSG_TRUNC-写入缓冲区?

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一起用于接收带外数据时也具有这种效果 这是否意味着不会写入提供的缓冲

我刚刚在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

下面是完整的代码:

/* 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;
}