Я только что столкнулся с неожиданным переполнением буфера, пытаясь использовать флаг MSG_TRUNC в recv на сокете TCP.Linux TCP recv() с MSG_TRUNC - записывает в буфер?
И, похоже, это происходит только с gcc (не clang) и только при компиляции с оптимизацией.
По этой ссылке: http://man7.org/linux/man-pages/man7/tcp.7.html
Начиная с версии 2.4, Linux поддерживает использование MSG_TRUNC в флагов аргумента RECV (2) (и recvmsg (2)). Этот флаг заставляет принятые байты данных отбрасываться, а не передаваться обратно в буфере, предоставляемом вызывающим абонентом. Начиная с Linux 2.4.4, MSG_PEEK также имеет этот эффект при использовании совместно с MSG_OOB для приема внеполосных данных.
Означает ли это, что поставляемый буфер не будет записан? Я так и ожидал, но был удивлен. Если вы передаете буфер (ненулевой указатель) и размер больше размера буфера, это приводит к переполнению буфера, когда клиент отправляет нечто большее, чем буфер. На самом деле это не означает, что сообщение записывается в буфер, если сообщение мало и помещается в буфер (без переполнения). Видимо, если вы передадите нулевой указатель, проблема исчезнет.
Клиент - это простой netcat, отправляющий сообщение размером более 4 символов.
код сервера основан на: http://www.linuxhowtos.org/data/6/server.c
Измененное чтения, чтобы Recv с MSG_TRUNC, и размер буфера до 4 (bzero до 4, а).
Составлено на Ubuntu 14.04. Эти сборники не работают отлично (без предупреждения):
Gcc -o server.x server.c
лязг -o server.x server.c
лязг -O2 server.x server.c (?)
Это багги сборник, он также дает предупреждение намекая о проблеме:
НКУ -O 2 -o server.x server.c
В любом случае, как я уже говорил, изменение указателя на нуль устраняет проблему, но является ли это известной проблемой? Или я пропустил что-то на странице руководства?
UPDATE:
Переполнение буфера происходит также с помощью GCC -O1. Вот предупреждение компиляции:
В функции «RECV», встраиваемое от «Главного» в server.c: 47: 14: /USR/включать/x86_64-Linux-ГНУ/биты/socket2.h: 42: 2: предупреждение: вызов «__recv_chk_warn» с предупреждением атрибута: recv вызывается с большей длиной, чем размер целевого буфера [включен по умолчанию] return __recv_chk_warn (__fd, __buf, __n, __bos0 (__buf), __flags) ;
Здесь переполнение буфера:
./server.x 10003 * переполнение буфера обнаружено *: ./server.x концевыми ======= 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] ======= памяти карта: ======== 00400000-00401000 г-хр 00000000 8:01 17732> /tmp/server.x ... больше сообщений здесь Прерванный (ядро сбрасывали)
И НКУ версия:
НКУ (Ubuntu 4.8.4-2ubuntu1 ~ 14.04.3) 4.8.4
Буфер и RECV вызова:
символ буфера [4];
n = recv (newsockfd, buffer, 255, MSG_TRUNC);
И это, кажется, это исправить:
п = ПРИЕМ (newsockfd, NULL, 255, MSG_TRUNC);
Это не будет создавать каких-либо предупреждений или ошибок:
НКУ -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;
}
UPDATE: Случается также на Ubuntu 16.04, с GCC версии:
НКУ (Ubuntu 5.4.0-6ubuntu1 ~ 16.04.2) 5.4.0 20160609
* Какие * предупреждения вы получаете? Вы пытались включить еще больше предупреждений (например, «-Wall -Wextra -pedantic» или аналогичный)? И, пожалуйста, покажите свой фактический вызов 'recv' вместе с определением буфера. –
Кроме того, поскольку он, очевидно, работает, это, вероятно, не проблема с ядром, но с * компилятором *, поэтому можете ли вы рассказать нам, какую версию GCC вы используете? И вы пробовали с более поздними версиями GCC? Ранее? Попробовал отключить некоторые определенные оптимизации? Протестировано с помощью '-O1'? Протестировано с помощью «-O1», а затем включите один вариант оптимизации после другого, пока не получите проблему (так что вы знаете, какая причина является причиной)? –
Проблема вряд ли будет с компилятором. Это может быть с библиотекой C (что, возможно, слишком тонкое различие), но это скорее с * программой *. Чтобы осмысленно решить этот вопрос, нам нужен код, с помощью которого мы можем воспроизвести проблему. Если вы хотите, чтобы мы действительно смотрели на такой код, то представляйте его в виде [mcve]. –