Основы и применение статического анализа кода при
разработке ПО
Дмитрий Русаков
Когда-то давно
Программист Продукт
В начале 80-ых
Программист
Продукт
Менеджер
В начале 90-х
Программист
ПродуктМенеджер
Тестировщик
Современность
Программист
ПродуктМенеджер
Тестировщик
Качество ПО
• Каждому требуется своё. ПО, работающее на изолированном компьютере, и веб-сайт правительства
• Целостность• Доступность
• С усложнением инфраструктуры усложнились требования к качеству ПО. Теперь не достаточно выполнять свои функции правильно, необходимо учитывать возможные нападения.
• Принципиальная невозможность добиться абсолютно правильной программы: машина Тьюринга, Microsoft.
• Целостность• Доступность• Конфиденциальность
Факторы, влияющие на уязвимость ПО
• Ошибки программирования (переполнение буфера),• Ошибки архитектуры (хранение пароля администратора на компьютере пользователя, реализация
контроля доступа на стороне клиента),• Некорректное применение (использование для разграничения доступа программы, в которой нет
аутентификации),• Некорректные настройки (отсутствие запрета модификации настроек безопасности для пользователя),• и т.д.
Тестирование
• С виду наиболее простой подход.• Позволяет контролировать качество вариантов использования, пришедших в голову тестировщику.• Наиболее проработанный и везде применяемых подход (функциональное тестирование, нагрузочное
тестирование, регрессионное тестирование, Unit Test).• Сложность проведения качественного тестирования
Ревизия
• Трудоёмкость• Требует определения целей
Типизированные языки программирования и контроль времени компиляции
• Позволяет выявлять наиболее простые «опечатки» программиста.• Метод быстрой разработки: недостатки.
Встраивание проверок и ограничений в код, использование "безопасных" функций и языков высокого уровня
• Встраивание проверок и ограничений позволяет выявлять проблемы во время исполнения и реагировать на них (контроль переполнения буфера в Visual С++).
• Использование «безопасных» функций позволяет избежать типичных ошибок программирования• char * strcpy ( char * destination, const char * source );• char * strncpy ( char * destination, const char * source, size_t num );
№3 в Top 25 CWE: «Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')»
• Языки высокого уровня позволяют детальнее продумывать архитектуру, не заботясь о деталях реализации.
Доказательство правильности свойств программ
• Минус: возможность работы только с небольшими программами• Требуется высокая компетенция экспертов• Формальное доказательство• Model Checking• Pi-calculus
Статический анализ
• Выявление «нехитрых» ошибок• Значительное количество ложных срабатываний
Исполнение в виртуальной среде
• Песочницы
Статический анализ кода
• Анализ, проводимый путём исследования исходных текстов или исполняемых файлов БЕЗ запуска кода.• Автоматическое выявление возможных уязвимостей кода БЕЗ исполнения кода.
• Статический анализ показывает на возможный недочёт.• do {} while true;
• char *p, *k;• p=k;• *k=10;• If ( p ) {};
Лексический контроль заданных правил
• Фактически – применение регулярных выражений.
• k = j/0;
• i = 0;• …• k = j/i;
• Сообщения компиляторов• Большинство (простейшие) утилиты поиска недостатков кода• Утилиты проверки правил оформления кода
Анализ программы без учета потока управления
• Прямой обход результатов лексического, синтаксического или семантического разбора.
• i = 0;• …• k = j/i;
• Не используемые переменные, «устаревшие» функции,• Сообщения умных компиляторов• Lint• CppCheck
Анализ программ с учётом потока управления
• Прямой обход результатов лексического, синтаксического или семантического разбора.
• i = 0;• …• k = j/i;
• Использование переменной до присвоения ей значения, недостижимый код• Klockwork, Coverity, VivaCore• Pvs-studio
Анализ аннотированного кода
• Аннотирование кода позволяет упростить работу алгоритма
• int readData( __out_bcount_part( maxLength, *length ) void *buffer, const int maxLength, int *length );
• Visual Studio (PreFast)
Примеры результатов PVS-studio
• There are identical sub-expressions 'height <= 0' to the left and to the right of the '||' operator.
• if (width <= 0 || height <= 0 || !data• || INT_MAX/sizeof(uchar *) < uint(height)• || INT_MAX/uint(depth) < uint(width)• || bpl <= 0• || height <= 0• || bpl < min_bytes_per_line• || INT_MAX/uint(bpl) < uint(height))• return d;
• This is a nonsensical comparison: pointer >= 0.• MessageItem *ContextItem::findMessage(const QString &sourcetext, const QString &comment) const• if (c->findMessage(m->text(), m->comment()) >= 0)
Примеры результатов PVS-studio
• The 'throw' operator inside the destructor should be placed within the try..catch block. Raising exception inside the destructor is illegal. • # define QT_RETHROW throw
• QObject::~QObject()• {• if (d->isSignalConnected(0)) {• QT_TRY {• emit destroyed(this);• } QT_CATCH(...) {• // all the signal/slots connections are still in place - if we don't• // quit now, we will crash pretty soon.• qWarning("Detected an unexpected exception in ~QObject while emitting destroyed().");• QT_RETHROW;• }• }• }
Примеры результатов PVS-studio
• Pointer to local variable 'tmp' is stored outside the scope of this variable. Such a pointer will become invalid.•
• QList<QGraphicsItem *> QGraphicsSceneBspTree::items(const QRectF &rect, bool onlyTopLevelItems) const• {• QList<QGraphicsItem *> tmp;• findVisitor->foundItems = &tmp;• findVisitor->onlyTopLevelItems = onlyTopLevelItems;• climbTree(findVisitor, rect);• // Reset discovery bits.• for (int i = 0; i < tmp.size(); ++i)• tmp.at(i)->d_ptr->itemDiscovered = 0;• return tmp;• }
Примеры результатов PVS-studio
• Pointer to local variable 'tmp' is stored outside the scope of this variable. Such a pointer will become invalid.•
• QList<QGraphicsItem *> QGraphicsSceneBspTree::items(const QRectF &rect, bool onlyTopLevelItems) const• {• QList<QGraphicsItem *> tmp;• findVisitor->foundItems = &tmp;• findVisitor->onlyTopLevelItems = onlyTopLevelItems;• climbTree(findVisitor, rect);• // Reset discovery bits.• for (int i = 0; i < tmp.size(); ++i)• tmp.at(i)->d_ptr->itemDiscovered = 0;• return tmp;• }
Примеры результатов CppCheck
• (error) Uninitialized variable: cc
int wfp_dispatch(wfp *p, int cnt, wfp_handler callback, u_char *user){
int cc;if (p->cc == 0) {
cc = p->packet->ulBytesReceived;} else
bp = p->bp;
#define bhp ((wfp_hdr *)bp)ep = bp + cc;
}
Примеры результатов CppCheck
• (error) Uninitialized variable: cc
int wfp_dispatch(wfp *p, int cnt, wfp_handler callback, u_char *user){
int cc;if (p->cc == 0) {
cc = p->packet->ulBytesReceived;} else
bp = p->bp;
#define bhp ((wfp_hdr *)bp)ep = bp + cc;
}
Примеры результатов CppCheck
• (error) Mismatching allocation and deallocation: reason
if (m_minorVerNum >= 8) {StringStorage errorMessage(e.getMessage());size_t reasonLen = errorMessage.getLength();char *reason = new char[reasonLen + 1];try {
if (errorMessage.toAnsiString(reason, reasonLen + 1)) {m_output->writeUInt32(1); m_output->writeUInt32(reasonLen);m_output->writeFully(reason, reasonLen);
}} catch (...) {
delete reason;throw;
}delete reason;
}
Примеры результатов CppCheck
• (error) Mismatching allocation and deallocation: reason
*/static int file_agent_save_file(FileInfo *file, char *capture_dir){
FILE *fh;fh = fopen(filename, "w");
while (file_mem){
if (!buff || !size )return -1;
}
fclose(fh);return 0;
}