Почему это не ошибка в qmail? - PullRequest
7 голосов
/ 03 апреля 2010

Я читал DJB "Некоторые мысли о безопасности после десяти лет Qmail 1.0" , и он перечислил эту функцию для перемещения дескриптора файла:

int fd_move(to,from)
int to;
int from;
{
  if (to == from) return 0;
  if (fd_copy(to,from) == -1) return -1;
  close(from);
  return 0;
}

Мне пришло в голову, что этот код не проверяет возвращаемое значение close, поэтому я прочитал справочную страницу для close (2), и кажется, что может завершиться с ошибкой EINTR, в которой В этом случае подходящим поведением может быть повторный вызов close с тем же аргументом.

Поскольку этот код был написан кем-то с гораздо большим опытом, чем я, как в C, так и в UNIX, и, кроме того, он оставался неизменным в qmail более десяти лет, я предполагаю, что должен быть какой-то нюанс, который мне не хватает, который делает этот код правильный. Кто-нибудь может объяснить мне этот нюанс?

Ответы [ 4 ]

2 голосов
/ 03 апреля 2010

У меня есть два ответа:

  1. Он пытался сделать вывод о выделении общего кода, и часто в таких примерах пропускается проверка ошибок для краткости и ясности.
  2. close (2) может вернуть EINTER, но делает ли это на практике, и если да, что бы вы разумно сделали? Повторить попытку? Повторить до успеха? Что делать, если вы получаете EIO? Это может означать почти все, поэтому у вас действительно нет разумных возможностей, кроме как войти в него и двигаться дальше. Если вы повторите попытку после EIO, вы можете получить EBADF, тогда что? Предположим, что дескриптор закрыт и двигаться дальше?

Каждый системный вызов может возвращать EINTR, особенно тот, который блокирует чтение (2), ожидая медленного человека. Это более вероятный сценарий, и хорошая подпрограмма «получить данные из терминала» действительно проверит это. Это также означает, что write (2) может завершиться ошибкой, даже при записи файла журнала. Вы пытаетесь записать ошибку, сгенерированную регистратором, или вы просто должны отказаться?

1 голос
/ 03 апреля 2010

Когда файловый дескриптор дублируется, как в функции fd_copy или dup2, вы получите более одного файлового дескриптора, ссылающегося на одну и ту же вещь (т. Е. Тот же struct file в ядро). Закрытие одного из них просто уменьшит его счетчик ссылок. Никакая операция не выполняется над базовым объектом, если только это не закрытие last . В результате такие условия, как EINTR и EIO невозможны.

0 голосов
/ 26 июля 2010

Только сломанные юниты когда-либо возвращают EINTR без явного запроса на это. Разумная семантика для signal() включает перезапускаемые системные вызовы («стиль BSD»). При создании программы в системе с семантикой sysv (прерывание сигналов) вы всегда должны заменять вызовы на signal() вызовами на bsd_signal(), которые можно определить в терминах sigaction(), если они не существуют.

Стоит также отметить, что системы no вернут EINTR при получении сигнала, если вы не установили обработчики сигналов. Если действие по умолчанию остается на месте или если для сигнала не задано действие, системные вызовы невозможно прервать.

0 голосов
/ 03 апреля 2010

Другая возможность состоит в том, что его функция используется только в приложении (или его части), которое сделало что-то, чтобы гарантировать, что вызов не будет прерван сигналом. Если вы не собираетесь делать что-то важное с сигналами, то вам не нужно реагировать на них, и, возможно, имеет смысл замаскировать их все, а не оборачивать каждый отдельный системный вызов блокировки при повторной попытке EINTR. За исключением, конечно, тех, которые вас убьют, так что SIGKILL и часто SIGPIPE, если вы справитесь с этим, выйдя вместе с SIGSEGV и подобными фатальными ошибками, которые в любом случае никогда не будут доставлены в правильное приложение пользовательского пространства.

В любом случае, если он говорит только о безопасности, то вполне возможно, что ему не придется повторять close. Если закрыть EIO не удастся, то он не сможет повторить попытку, это будет постоянный сбой. Следовательно, для правильности его программы необязательно, чтобы close был успешным. Вполне возможно, что для правильности его программы необязательно повторять close в EINTR.

Обычно вы хотите, чтобы ваша программа делала все возможное, чтобы добиться успеха, а это означает повторную попытку EINTR. Но это отдельная проблема от безопасности. Если ваша программа разработана таким образом, что сбой какой-либо функции по какой-либо причине не является недостатком безопасности, то, в частности, тот факт, что случается с ошибкой EINTR, а не по постоянной причине, не является изъян. DJB, как известно, был довольно самоуверенным, поэтому я не удивлюсь, если он докажет какую-то причину, по которой ему не нужно , чтобы повторить попытку, и поэтому он не беспокоится, даже если это будет позволить своей программе успешно очистить дескриптор в определенных ситуациях, когда, возможно, в данный момент происходит сбой (например, если в критический момент пользователь явно отправил безвредный сигнал с kill).

Редактировать: мне приходит в голову, что повторная попытка EINTR потенциально может быть ошибкой безопасности при определенных условиях. Он вводит новое поведение в этот раздел кода: он может бесконечно зацикливаться в ответ на поток сигнала, когда ранее он делал одну попытку close и затем возвращался. Я точно не знаю, что это вызовет проблемы с qmail (в конце концов, close сам не дает никаких гарантий относительно того, как скоро он вернется). Но если сдача после одной попытки действительно облегчает анализ кода, то это может быть разумным шагом. Или нет.

Вы можете подумать, что повторная попытка предотвращает ошибку DoS, когда сигнал вызывает ложный сбой. Но повторная попытка допускает еще один (более сложный) недостаток DoS, когда поток сигнала вызывает неопределенный останов. С точки зрения бинарного «может ли это приложение быть DoSed?», Что является абсолютным вопросом безопасности, который интересовал DJB, когда он писал qmail и djbdns, это не имеет значения. Если что-то может случиться один раз, то обычно это означает, что это может случиться много раз.

...