20 мая 2008 г.

Ошибки кодирования

Очередной багфиксинг показал, что за 30 лет мало что изменилось :)

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

И вот, модуль логгирования вдруг стал время от времени "падать" в совершенно непредсказуемые моменты. Оказалось, что виноват в этом один из компонентов, который вместо осмысленного текста посылает в модуль логгирования мусор. У компонента проблема, но почему модуль-то падает?..

Ошибка стара, как Си :) Разработчик модуля логгирования реализовал функцию примерно так:

void logMessage (const char *str)
{
  printf(str);
}

Видно ошибку? Проблема тут в том, что текст сообщения напрямую подается в функцию printf, минуя использование форматных строк типа "%s\n". Если подавать на вход функции осмысленный текст, то все будет хорошо, до тех пор пока в тексте сообщения не встретится "%s". В этом случае функция printf попытается сформировать новую строку, предполагая, что в стеке находятся дополнителльные аргументы. А аргументов-то там и нет. Вот тут-то printf и упадет с большой долей вероятности (скорее всего это произойдет, когда printf попытается вызвать strlen для значения, которое достанет из стека).

В нашем случае на вход функции подавался случайный мусор, поэтому время от времени в нем встречалась последовательность "%s", и модуль вываливался с ошибкой Segmentation fault.

Вот правильное использование printf для нашей функции логгирования:

void logMessage (const char *str)
{
  printf("%s", str);
}

Вызов printf и ей подобных должен выглядеть именно так и никак иначе.

Читать об уязвимостях printf.

Технологии мчатся вперед, а ошибки не меняются :)