Файл: Тема 3 Схемы и алгоритмы анализа ошибок, использование баз знаний 1 Классификация ошибок.docx
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 04.12.2023
Просмотров: 114
Скачиваний: 3
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
Тема 2.3 Схемы и алгоритмы анализа ошибок, использование баз знаний
1 Классификация ошибок
Неотъемлемой частью процесса сопровождения программного обеспечения (ПО) является сбор и консолидация данных об ошибках, возникающих в процессе его работы.
Источниками данных об ошибках могут быть как пользователи ПО, так и средства мониторинга вычислительных систем.
Всё множество программных ошибок, приводящих к некорректному поведению программы во время исполнения, можно разделить на следующие классы по видам вредоносного воздействия:
-
ошибки, приводящие к порче пользовательских данных в процессе обработки: целочисленное переполнение, порча данных в оперативной памяти, обращение к неинициализированному блоку памяти, обращение к памяти по неинициализированному или висящему указателю (англ. – dangling pointer), фальсификация данных (англ. - request forgery) и др.; -
ошибки, приводящие к неавторизованному доступу к пользовательским данным: получение неавторизованного доступа к базе данных, получение неавторизованного доступа к информации в оперативной или постоянной памяти вычислительного устройства, получение повышенного уровня привилегий доступа к данным и др.; -
ошибки, приводящие к исчерпанию системных ресурсов, таких как память на куче, файлы, сокеты и др.; -
ошибки, приводящие к аварийному завершению исполнения программы: доступ к области памяти, не принадлежащей программе, деление на ноль и др.; -
ошибки, приводящие к исполнению злонамеренного кода: перехват потока управления злонамеренным кодом, исполнение злонамеренного кода на стороне клиента, внедрение в исполнение команды в командной строке и др.
Причины появления ошибок в программах:
-
Влияние квалификации команды разработчиков программы:
-
плохой или недостаточный уровень проработки архитектуры программы: плохо спроектированная архитектура программы может привести к внесению ошибок в процессе развития программной системы; -
пренебрежительное отношение программистов к результату своей работы: вместо тщательного планирования изменений в программе применяется метод быстрого кодирования с последующим исправлением ошибок, найденных путём тестирования программы.
-
Уделяется мало внимания различным уровням тестирования программы в процессе разработки:
-
недостаточное количество и качество автоматических тестов на компоненты (модульных тестов), что приводит к позднему обнаружению нарушения контракта использования модуля и функции; -
недостаточность или отсутствие интеграционных тестов, что приводит к интеграционным ошибкам в процессе изменения кода компонентов, в том числе разделяемых компонентов операционной системы, не учитывающих контекст их использования; -
недостаточный уровень тестирования программы на устойчивость при обработке некорректных внешних данных.
-
Влияние изменений, вносимых в среду исполнения программы. Многие современные программы используют разделяемые библиотеки, как правило, входящие в комплект поставки операционных систем или используемые совместно различными программами. Изменение в коде разделяемых библиотек при недостатке интеграционного тестирования может привести к неправильной работе программы.
Всё множество причин появления ошибок в программах в итоге сводятся к одной – недостаточной квалификации разработчиков программного обеспечения. В связи с этим становится очевидной необходимость создания автоматических средств обнаружения ошибок в программном обеспечении, которые позволят обнаруживать ошибки на различных этапах жизненного цикла ПО.
На ранних этапах развития вычислительной техники применялся процесс «Кодирование и исправление», который заключался в кодировании программы с последующим этапом избавления программы от ошибок (англ. debugging) программистом, написавшим программу.
Позже стали применяться методы тестирования программ для подтверждения того, что программа делает именно то, что требуется, и не делает ничего, для чего она не предназначена.
Инспекция кода и тестирование относятся к двум классам методов обнаружения ошибок в программах, отличающихся способом обнаружения ошибок в программе: статические методы – без запуска программы на исполнение, динамические методы – по результатам или в процессе исполнения программы.
2 Статический анализ обнаружения ошибок
В связи с развитием алгоритмов символьного исполнения и появлением инструментов для решения формул в теориях в последнее время стали появляться инструменты статического анализа третьего поколения, которые объединяют классические методы анализа путей исполнения программы и символьное исполнение программы в комбинации с применением решателей.
В итоге инструменты статического анализа кода можно классифицировать по следующим признакам:
-
способ анализа: автоматический или полуавтоматический; -
язык программирования: один, несколько, анализ промежуточного представления, анализ исполняемого кода; -
типы обнаруживаемых программных ошибок: фиксированный список, возможность создания дополнительных детекторов ошибок; -
модель кода программы: текстовый поиск, синтаксический анализ, анализ дерева абстрактного синтаксиса, анализ путей исполнения, межпроцедурный анализ. (2ис1)
Алгоритм «Чувствительный к путям анализ»
Анализ потока без использования дополнительного анализа условий выполнения путей не всегда может быть применён для точного определения наличия ошибки в программе. Классическим примером для иллюстрации необходимости проведения анализа, чувствительного к путям исполнения, является последовательное исполнение двух условных операторов, или ромбовидного графа потока управления (листинг 7):
Листинг 7. Пример программы, требующей анализа путей
1: char buffer[10]
2: void init(char **p, int a)
3: { 4: if(a > 0)
5: *p = &buffer[0]; // *p <> NULL if a > 0
6: if(a >= 0)
7: **p = ‘\0’; // deref(*p) if a == 0
8: }
Для того чтобы утверждать об отсутствии ошибки разыменования нулевого указателя на строке 7, необходимо провести анализ путей исполнения подпрограммы init и собрать условия, при которых значение указателя не равно нулю.
Межпроцедурный анализ
Идея межпроцедурного анализа заключается в разбиении программы на блоки, соответствующие подпрограммам, и проведение анализа каждого блока отдельно. Стоит отметить, что простого анализа каждой подпрограммы в отдельности может быть недостаточно для обнаружения определённого типа дефектов.
Рассмотрим пример на листинге 8.
Листинг 8. Пример межпроцедурной трассы ошибки
1: char * init(size_t n) // init может вернуть ноль
2: {
3: char *p = malloc(n); // p может быть равно нулю
4: if(0 != p) // Если p не равен нулю
5: *p = '\0'; // Обнулить строку
6: return p; // возможен возврат нуля
7: }
8: int main(int argc, char* argv[])
9: {
10: char* buf;
11: buf = init(strlen(argv[0])); // buf может быть равен нулю
12: strcpy(buf, argv[0]); // разыменование возможного
13:} // нуля
3 Динамические методы обнаружения ошибок
К динамическим методам обнаружения ошибок относится метод Фаззинг. Развитие методов фаззинга программ привело к созданию специфических инструментов, предназначенный для фаззинга веб-браузеров, созданный группой исследователей для тестирования каскадных стилевых таблиц.
Фаззинг - техника тестирования программ в процессе исполнения путем передачи на вход полунеправильных внешних данных с отслеживанием состояния программы, как правило, при помощи инструментов отладки с целью обнаружения исключительных ситуаций, таких как нарушение доступа к памяти.
Мутационный фаззинг использует в качестве начальных значений корректные внешние данные, которые в дальнейшем изменяются (мутируются) с целью обнаружения полунеправильных внешних данных.
Умный фаззинг (англ. intelligent fuzzing) использует некоторую модель, описывающую внешние данные, для генерации полунеправильных внешних данных. Данный вид фаззинга показал хорошие результаты при фаззинге сетевых протоколов и форматов данных.
В основе Фаззинга с подкреплением (англ. – feedback fuzzing) лежат алгоритмы адаптации процесса изменения потока внешних данных на основе информации, полученной от анализируемой программы.
Фаззинг «стеклянного» («белого») ящика (англ. glass -box fuzzing, white-box fuzzing), как правило, реализован в виде динамического символьного исполнения программ.
Распределённый фаззинг(англ. distributed fuzzing) – фаззинг одной программы, проводящийся на нескольких вычислительных узлах одновременно.
Динамическое символьное исполнение
Изначально методы символьного исполнения программ применялись для генерации тестовых наборов для программы с целью повышения тестового покрытия базовых блоков программы методом «белого ящика», или структурного тестирования программы.
Перед началом процесса генерации тестового покрытия для программы производится инструментация программы (англ. instrumentation – добавление специальных инструментов, измеряющих и записывающих различные характеристики в процессе испытаний), то есть добавление в код программы функциональности, задача которой состоит в сборе информации о выполненных программой инструкциях над внешними данными, представляющимися в виде символьных переменных. Далее производятся псевдослучайная генерация начального набора внешних данных для программы и запуск программы с целью сбора последовательности выполненных инструкций, которая в дальнейшем преобразуется в условие пути исполнения. После этого
производится добавление в условие пути предиката, который позволяет инвертировать значение условия для одного из условных переходов.
В связи с вышеуказанными недостатками отдельных методов анализа программ становится очевидной идея комбинирования различных методов анализа с целью компенсации их недостатков.
4 Комбинированные методы
Альтернативный метод объединения техники статического анализа и динамического символьного исполнения, посвящённой подтверждению ошибок использования указателя на выделенную память после её освобождения. Метод основан на экскавации срезов графа потока исполнения, содержащих операции выделения, использования (обращения по указателю к блоку памяти) и освобождения динамической памяти с последующим применением направленного динамического анализа, который в качестве эвристики выбора пути использует для каждого условного перехода на пути оценку, основанную на расстоянии, с целью выбора наиболее короткого пути на каждой следующей итерации анализа.
Исходя из обзора методов анализа программ, наиболее перспективным направлением развития методов обнаружения ошибок в программах представляется направление совмещения различных методов анализа программ с целью повышения точности результата и производительности инструментов анализа программ.
Общий алгоритм комбинированного анализа
Для реализации метода классификации предупреждений об ошибках решены несколько задач, и для их решения разработан набор алгоритмов, позволяющих достичь цели классификации предупреждений о программных ошибках. Результаты разработки алгоритмов комбинированного анализа изложены в публикациях автора данной диссертации [169, 170, 171].
Общий алгоритм, лежащий в основе предлагаемого метода, состоит из нескольких этапов.
Алгоритм 1. Общий алгоритм комбинированного анализа:
-
Обнаружение потенциальных ошибок в программе методами статического анализа. -
Статический анализ исполняемого кода программы для получения сокращённого графа потока программы с целью вычисления путей, содержащих трассу событий для каждой найденной с помощью статического анализа ошибки. -
Вычисление расстояния от каждого базового блока программы, лежащего на путях, вошедших в сокращённый граф потока программы, до точки на трассе событий для каждой ошибки, найденной с помощью статического анализа. -
Реализация направленного динамического символьного исполнения программы с целью генерации внешних данных, приводящих к исполнению программы по путям, достигающим трассу ошибки. -
Генерация внешних данных программы, нарушающих предикат безопасности в точке реализации ошибки. -
Проверка проявления ошибки в процессе исполнения программы на внешних данных, сгенерированных на этапе 5.