Вебинар: Использование статических анализаторов кода при разработке безопасного ПО - 19.12
Примерно неделю назад была опубликована статья "Три интервью о статических анализаторах кода", в которой на читательский суд были представлены точки зрения опытных программистов из Acronis, AlternativaPlatform и НПО "Эшелон" о методике разработки ПО, а также некоторые их размышления относительно использования статических анализаторов кода.
Поскольку спонсором статьи выступила компания ООО "СиПроВер" - разработчик статического анализатора PVS-Studio, - я решил попросить Андрея Карпова (технический директор) также ответить на некоторые вопросы. А именно - прокомментировать интересные моменты всех трёх интервью. Плюс сделать обращение к коллегам и читателям. Получился вот такой интересный результат.
Пару раз на конференции при неформальном общении в холле или за обеденным столом меня спрашивали: "Неужели кто-то ещё программирует на Си++?". И искренне удивлялись, когда я отвечал: "да, и это один из самых используемых языков". Просто он как-то сегодня не на слуху. Кругом php, ruby, go. Кажется, что Си++ - это "было давно и не правда". И мне приятно, что люди лишний раз увидят в статье, что, например, Acronis Backup написан на Си++ и над этим постоянно трудится 70 программистов. Сам я о будущем Си и Си++ не переживаю. Мне просто удивительно, как так выходит, что многие считают Си++ мёртвым языком.
Приятно было также услышать, что в Acronis широко применяется практика Code Review. Часто этот метод повышения качества программ недооценивается, или считается, что он отнимает слишком много времени. Скупой платит дважды.
Кстати, я знаю по крайней мере один пример, когда иногда умножать sizeof на sizeof всё-таки имеет практический смысл. Например, такое умножение получается, когда один sizeof() используется для взятия количества элементов в массиве. Имеется в виду вот это:
template <typename T, size_t N>
char (&ArraySizeHelper(T (&array)[N]))[N];
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
Такой 'arraysize' защищает от случайной передачи в качестве аргумента не массива, а обыкновенного указателя. Подробности здесь.
В результате, вполне может получиться конструкция вида: "sizeof(float) * (sizeof(ArraySizeHelper(array)))". Но анализатор PVS-Studio знает про такие случаи и не выдаёт предупреждений.
С Java я не знаком, поэтому не могу прокомментировать, насколько хорошо язык защищает от ошибок. Конечно, уже одно отсутствие ручного управления памятью сильно упрощает жизнь. Однако, я думаю, часть ошибок не зависит от языка. Например, если это результаты Copy-Paste. Думаю, использование статического анализатора для поиска опечаток было бы весьма уместно и для Java. Но я опять-таки не знаю, что предлагают для Java существующие анализаторы кода.
Сразу чувствуется немного казённый стиль написания текста. Видимо, накладывает отпечаток специфика работы и тип документов, которые приходится подготавливать. С одной стороны, мне не нравятся такие тексты, так как их скучно читать. С другой стороны, я завидую. Текст производит ощущение солидности и серьезности проделываемой работы. У нас такого про PVS-Studio нет. Мы много пишем статей про использование PVS-Studio, но почти ничего про сам анализатор и как он важен. Надо будет тоже попробовать пописать о PVS-Studio солидные тексты описательного плана.
Кстати, пользуясь случаем, хотел поднять вот какую тему. Наши пользователи или потенциальные пользователи совершенно не рассматривают PVS-Studio как инструмент, способный находить уязвимости. Я этого не понимаю. Да, мы не ищем закладки в коде. Мы ориентированы на поиск ошибок, а не дефектов, которые делают ПО уязвимым. Но всё равно я не понимаю такое деление на белое и чёрное. Ведь многие ошибки можно точно так же рассматривать как уязвимость. Достаточно только посмотреть на ошибку под другим углом.
Возьмём, например, проект UltimateTCPIP и найдем в нём с помощью PVS-Studio вот такую ошибку:
char *CUT_CramMd5::GetClientResponse(LPCSTR ServerChallenge)
{
...
if (m_szPassword != NULL)
{
...
if (m_szPassword != '\0')
{
...
}
V528 It is odd that pointer to 'char' type is compared with the '\0' value. Probably meant: *m_szPassword != '\0'. UTMail ut_crammd5.cpp 333
Мы говорим о такой ошибке просто, как об опечатке. Забыли разменивать указатель. В результате не выполняется проверка, что строка пустая. Код должен был быть таким:
if (*m_szPassword != '\0')
Но ведь, с другой стороны, это самая настоящая уязвимость. Оставим в стороне вопрос, можно ли этой уязвимостью воспользоваться и насколько она опасна. Главное то, что проверка на опечатку может выявить самую настоящую дыру в безопасности. Мало ли что пойдёт не так, если программа начнёт работать с пустым паролем.
Или ещё пример из PostgreSQL:
char *
px_crypt_md5(const char *pw, const char *salt,
char *passwd, unsigned dstlen)
{
....
unsigned char final[MD5_SIZE];
....
/* Don't leave anything around in vm they could use. */
memset(final, 0, sizeof final);
....
}
V597 The compiler could delete the 'memset' function call, which is used to flush 'final' buffer. The RtlSecureZeroMemory() function should be used to erase the private data. pgcrypto crypt-md5.c 157
Здесь PVS-Studio обнаруживает, что массив 'final' не обнуляется перед выходом из функции. Почему так, можно узнать из описания диагностики V597.
Вот мне и непонятно, почему диагностики PVS-Studio являются "недостаточно выявляющими уязвимости".
В целом со статическим анализом всё хорошо. Инструментарий в этом направлении быстро развивается и набирает большую популярность.
Хотелось бы, чтобы в России это тоже происходило побыстрее. У нас практически нет рынка для инструментов статического анализа. Об этом можно судить хотя бы по количеству посещений нашего сайта, скачиванию демонстрационных версий и статистике продаж. Половину всей активности создают посетители из России. Но количество российских клиентов составляет не 50%, а только чуть-чуть. Печально.
Текст в духе "используйте в работе статический анализ" прозвучит банально. Поэтому подниму нестандартную тему.
Желаю удачного общения между начальниками и подчинёнными. Часто указания начальства выглядят по меньшей мере странными. Но следует учитывать, что начальник часто обладает большим объёмом знаний по проекту в целом. И то, что кажется странным подчинённому, на высоком уровне может быть очень полезным или просто вынужденным. К сожалению, начальники программистов часто сами являются выходцами из программистов и потому тяготеют к интроверсии. Другими словами, ставя задачу, они не считают нужным объяснить, зачем всё это надо. Их надо понять и простить. А потом, задавая вопросы, понять, чем вызвано такое странное распоряжение. Скорее всего, начальник не против будет объяснить. Он просто забыл про это или "оптимизировал" время общения, сведя комплексную задачу к "сделай так". Соответственно, желаю и руководству не забывать объяснять свои шаги и решения.
И спасибо организатору интервью.
Итак, уважаемые читатели, можно сказать, что бонусные материалы были только что продемонстрированы Вам. Автор надеется, что Вам было интересно. На этом разрешите откланяться. Пишите качественный код и пользуйтесь самым широким спектром полезных инструментов. Всего доброго! Не прощаемся.
0