Проверил, помню ли я еще Си — TCP сервер с ошибками

18 сентября 2013

В свое время я довольно сильно увлекался всевозможной низкоуровщиной — ассемблерами (о чем как бы намекает доменное имя блога), сишечкой и так далее. Даже написал пару несложных драйверов для Windows. Но потом я открыл для себя Perl и понеслось.

Оказалось, что программы можно писать без ручного управления памятью, постоянной борьбы с переполнениями буферов и тп. Позже выяснилось, что выводить типы в уме и иметь побочные эффекты тоже не обязательно, и меня потянуло в сторону функциональщины. К Си, а также C++, я не возвращался (за исключением единичных случаев) в течение долгого времени.

А недавно я выбрал для легкого чтения перед сном книгу «Компьютерные вирусы и антивирусы, взгляд программиста». И такая ностальгия на меня нахлынула, что я решил сесть и написать небольшую программку на Си.

Программка банальнейшая — TCP сервер, спрашивающий имя пользователя и говорящий «привет, %пользователь»:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

void handle_error(char* err_fun) {
  printf("%s() failed, errno = %d", err_fun, errno);
  exit(1);
}

void child_proc(int sock) {
  size_t name_len;
  char msg[] = "What is your name?\n";
  char name_buff[256];
  char format_buff[256];
  write(sock, msg, sizeof(msg));
  name_len = read(sock, name_buff, sizeof(name_buff));
  if(-1 == name_len) {
    handle_error("read");
  }
  while(name_len > 0 &&
      ( '\n' == name_buff[name_len-1]  ||
        '\r' == name_buff[name_len-1])) {
    name_len--;
  }
  name_buff[name_len] = 0;
  sprintf(format_buff, "Hello, %s!\n", name_buff);
  write(sock, format_buff, strlen(format_buff));
  close(sock);
  exit(0);
}

int main() {
  struct sockaddr_in addr;
  int accepted_socket;
  int fork_pid;
  int listen_socket = socket(AF_INET, SOCK_STREAM, 0);
  if(-1 == listen_socket) {
    handle_error("socket");
  }

  addr.sin_family = AF_INET;
  addr.sin_port = htons(31337);
  addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  if(-1 == bind(listen_socket, (struct sockaddr*)&addr, sizeof(addr))){
    handle_error("bind");
  }

  if(-1 == listen(listen_socket, 100)) {
    handle_error("listen");
  }

  for(;;) {
    accepted_socket = accept(listen_socket, 0, 0);
    if(-1 == accepted_socket) {
      handle_error("accept");
    }

    fork_pid = fork();
    if(-1 == fork_pid) {
      handle_error("fork");
    } else if(0 == fork_pid) {
      close(listen_socket);
      child_proc(accepted_socket);
    } else {
      close(accepted_socket);
    }
  }
  exit(0);
}

Как выяснилось, что-то еще помнится. Писал я все это хозяйство минут тридцать, попутно заполняя пробелы в знаниях с помощью man-страниц и томика Стивенса, который в свое время я прочитал на одном дыхании и до сих пор бережно храню на почетной верхней книжной полке. Эх, наверное, все-таки, прикольно быть сишником…

Вы, конечно же, без труда найдете в этом коде три грубейшие ошибки. Я подскажу первую. Тут есть переполнение буфера. Пользователь может ввести имя длиной 256 байт, которые в сочетании с еще с девятью байтами форматной строки "Hello, %s!\n" переполнят буфер format_buff. Кстати, я обнаружил, что современные версии GCC вставляют рантайм проверки на переполнение буфера. В свое время еще говорили про хардварные защиты, что дескать все новые процессоры будут как-то автоматически детектить выполнение кода в стеке или что-то в этом роде. Кто следит за темой, переполнение буфера еще существует вообще?

Ну и, конечно же, я с нетерпением жду от вас описание еще как минимум двух ошибок, которые мне было лень исправлять в этом коде. В конце поста появятся ссылки на сайты и микроблоги тех, кто первый их опишет в комментариях.

Дополнение: Быстрее всех ошибки нашли smirn0v и rulexec. Молодцы! Описание ошибок вы найдете в заметке Типичные ошибки в сетевых приложениях на C/C++. Также вас может заинтересовать пост Асинхронная работа с сокетами на C/C++ с libevent.

Метки: .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.