Демонстрация: ошибка, найденная фаззером AFL++
Цель: показать студенту, как фаззер находит ошибку записи за пределы буфера в простой C++ программе и как программист исправляет такую ошибку.
FUZ1, пятый байт задает длину имени, дальше идут байты имени.
1. В чем ошибка
Программа читает размер имени из файла:
uint8_t declared_size = data[4];
Но затем использует это значение как лимит записи в массив фиксированного размера:
char name[16] = {};
name[16], name[17] и дальше, хотя допустимые индексы массива только 0..15.
2. Исходный и исправленный код
| Исходный вариант с ошибкой | Исправленный вариант |
|---|---|
uint8_t declared_size = data[4];
char name[16] = {};
// ОШИБКА: declared_size взят из файла
// и не ограничен размером name.
for (uint8_t i = 0;
i < declared_size && 5 + i < data.size();
++i) {
name[i] = static_cast<char>(data[5 + i]);
}
|
uint8_t declared_size = data[4];
char name[16] = {};
size_t available_size = data.size() - 5;
size_t copy_size = std::min<size_t>({
declared_size,
sizeof(name),
available_size
});
for (size_t i = 0; i < copy_size; ++i) {
name[i] = static_cast<char>(data[5 + i]);
}
|
3. Почему проверка была неполной
В исходном коде условие цикла проверяло только чтение из входного файла:
5 + i < data.size()
Эта проверка отвечает на вопрос: "Есть ли еще байт во входных данных?"
Но она не отвечает на другой важный вопрос: "Есть ли еще место в массиве name?"
4. Что делает исправление
Исправленный код считает безопасный размер копирования:
size_t copy_size = std::min<size_t>({
declared_size,
sizeof(name),
available_size
});
Берется минимум из трех значений:
| Значение | Что означает |
|---|---|
declared_size |
Сколько байт просит сам входной файл. |
sizeof(name) |
Сколько байт реально помещается в буфер назначения. |
available_size |
Сколько байт реально есть во входных данных после заголовка. |
name.
5. Как фаззер показывает проблему
Сборка с AFL++ и AddressSanitizer:
AFL_USE_ASAN=1 afl-clang-fast++ -g -O1 src/main.cpp -o build/demo_afl_asan
Запуск фаззера:
AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 AFL_SKIP_CPUFREQ=1 \ afl-fuzz -i in -o out_afl_asan_demo -- build/demo_afl_asan @@
Воспроизведение найденного падения:
build/demo_afl_asan out_afl_asan_demo/default/crashes/id*
Типичный результат:
ERROR: AddressSanitizer: stack-buffer-overflow WRITE of size 1 src/main.cpp:29:17 in parse_demo_format
6. Главный вывод
Учебный пример предназначен для демонстрации фаззинга собственного кода в изолированной среде.