I've just encountered a surprising buffer overflow, while trying to use the flag MSG_TRUNC in recv on a TCP socket.
And it seems to only happen with gcc (not clang) and only when compiling with optimization.
According to this link: http://man7.org/linux/man-pages/man7/tcp.7.html
Since version 2.4, Linux supports the use of MSG_TRUNC in the flags argument of recv(2) (and recvmsg(2)). This flag causes the received bytes of data to be discarded, rather than passed back in a caller-supplied buffer. Since Linux 2.4.4, MSG_PEEK also has this effect when used in conjunction with MSG_OOB to receive out-of-band data.
Does this mean that a supplied buffer will not be written to? I expected so, but was surprised. If you pass a buffer (non-zero pointer) and size bigger than the buffer size, it results in buffer overflow when client sends something bigger than buffer. It doesn't actually seem to write the message to the buffer if the message is small and fits in the buffer (no overflow). Apparently if you pass a null pointer the problem goes away.
Client is a simple netcat sending a message bigger than 4 characters.
Server code is based on: http://www.linuxhowtos.org/data/6/server.c
Changed read to recv with MSG_TRUNC, and buffer size to 4 (bzero to 4 as well).
Compiled on Ubuntu 14.04. These compilations work fine (no warnings):
gcc -o server.x server.c
clang -o server.x server.c
clang -O2 server.x server.c
This is the buggy (?) compilation, it also gives a warning hinting about the problem:
gcc -O2 -o server.x server.c
Anyway like I mentioned changing the pointer to null fixes the problem, but is this a known issue? Or did I miss something in the man page?
UPDATE:
The buffer overflow happens also with gcc -O1. Here is the compilation warning:
In function ‘recv’, inlined from ‘main’ at server.c:47:14: /usr/include/x86_64-linux-gnu/bits/socket2.h:42:2: warning: call to ‘__recv_chk_warn’ declared with attribute warning: recv called with bigger length than size of destination buffer [enabled by default] return __recv_chk_warn (__fd, __buf, __n, __bos0 (__buf), __flags);
Here is the buffer overflow:
./server.x 10003 * buffer overflow detected *: ./server.x terminated ======= Backtrace: ========= /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] ======= Memory map: ======== 00400000-00401000 r-xp 00000000 08:01 17732 > /tmp/server.x ... more messages here Aborted (core dumped)
And gcc version:
gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4
The buffer and recv call:
char buffer[4];
n = recv(newsockfd,buffer,255,MSG_TRUNC);
And this seems to fix it:
n = recv(newsockfd,NULL,255,MSG_TRUNC);
This will not generate any warnings or errors:
gcc -Wall -Wextra -pedantic -o server.x server.c
And here is the complete code:
/* 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;
}
UPDATE: Happens also on Ubuntu 16.04, with gcc version:
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.2) 5.4.0 20160609
-Wall -Wextra -pedantic
or similar)? And please show your actualrecv
call together with the definition of the buffer. – Some programmer dude-O1
? Tested with-O1
and then enabling one specific optimization option after another until you get the problem (so you know which one is the cause)? – Some programmer dude