
Автоматическая генерация фаззинг-драйверов на основе UT
UTopia — это инструмент для автоматической генерации фаззинг-драйверов из модульных тестов.
UTopia позволяет разработчикам выполнять фаззинг-тестирование без специальных знаний о написании фаззеров. Даже разработчики, знакомые с фаззингом, могут значительно сэкономить время благодаря автоматической генерации фаззинг-драйверов.
UTopia поддерживает C/C++ библиотеки, для которых есть модульные тесты на GoogleTest, Boost.Test или Tizen TCT.
Чтобы увидеть ошибки, найденные с помощью UTopia, посетите страницу Trophy. Там также можно увидеть некоторые фаззеры на основе UTopia.
Для удобной настройки мы предоставляем docker-образ для запуска UTopia. Вы можете собрать UTopia и генерировать/запускать фаззеры внутри docker-контейнера.
Соберите docker-образ с помощью команды ниже.
docker buildx build -f docker/Dockerfile -t utopia . #llvm-10
docker buildx build --build-arg LLVM_VERSION=12 -f docker/Dockerfile -t utopia . #llvm-12
UTopia лучше всего работает с LLVM версии 10. Также подтверждено, что он проходит модульные тесты на LLVM версии 12. Поэтому мы рекомендуем использовать LLVM 10, но при желании можете попробовать LLVM версии 12.
UTopia зависит от LLVM, Protobuf и GoogleTest. Вы можете установить зависимости вручную, но мы рекомендуем использовать предоставленный docker-образ.
После клонирования репозитория инициализируйте и обновите все подмодули:
git submodule update --init --recursive
Чтобы собрать UTopia, выполните следующие шаги cmake.
cd $UTOPIA_HOME_DIR
cmake -B build -S .
cmake --build build -j$(nproc)
Для некоторых выбранных проектов можно использовать вспомогательный скрипт, чтобы запустить наш инструмент без дополнительных усилий. Пожалуйста, обратитесь к helper/README.md. Для остальных проектов ознакомьтесь со следующей инструкцией.
Target Analyzer анализирует код целевой библиотеки и формирует результат в виде json-файла. Обязательные параметры командной строки приведены ниже.
target_analyzer --db ${builddb_path} --extern ${extern_path} --public ${api_json_path} --out ${output_path}
Build db — это json-файл, содержащий пути к AST- и IR-файлам кода целевой библиотеки. Его формат выглядит следующим образом.
{
"bc": "/root/fuzz-test-generation/exp/sample/output/bc/libcommon.a.bc",
"ast": [
"/root/fuzz-test-generation/exp/sample/libcommon.a_ast/codec/common/src/ast1.o.ast",
"/root/fuzz-test-generation/exp/sample/libcommon.a_ast/codec/common/src/ast2.o.ast"
],
"project_dir": "/root/fuzz-test-generation/exp/sample"
}
Путь к LLVM bitcode-файлу конкретной библиотеки должен быть указан с помощью ключевого слова "bc". Пока мы принимаем только один bitcode-файл, поэтому вы можете использовать llvm-link, чтобы объединить несколько bitcode-файлов в один.
Пути к AST-файлам конкретной библиотеки должны быть указаны с помощью ключевого слова "ast". Обратите внимание, что указанный bitcode-файл и AST-файлы должны быть созданы из одних и тех же исходных кодов конкретной библиотеки.
Target Analyzer принимает отчёты target analyzer других библиотек для получения более точного результата. Этот путь должен быть путём к каталогу, в котором хранятся другие отчёты; допускается наличие отчётов более чем одной библиотеки.
Имена API-функций для анализа. Файл должен быть в формате json, как показано ниже.
{
"libcommon.a": [
"API1",
"API2",
"API3"
]
}
Получить список API для конкретной библиотеки можно с помощью команды ниже.
nm --no-demangle --defined-only -g ${librarypath} | awk '$2=="T" {k=""; for(i=3;i<=NF;i++) k=k $i""; print k}'
Свойство Direction — это важный параметр, используемый target analyzer. Оно показывает, используется ли параметр для чтения (Dir_In), записи (Dir_Out) или одновременного чтения и записи (Dir_In | Dir_Out) внутри функции. Target analyzer описывает это свойство с помощью перечислимого типа, состоящего из следующих элементов:
enum Dir {
Dir_NoOp = 0x000, // No operation
Dir_In = 0x100, // Input direction
Dir_Out = 0x010, // Output direction
Dir_Unidentified = 0x001 // Unidentified direction
};
Например, в приведённом фрагменте JSON-файла:
{
"Direction": {
"BF_crypt(0)": 256,
"BF_crypt(1)": 272,
"BF_crypt(2)": 272,
"BF_decode(1)": 272
}
}
Эта нотация помогает понять, как используется каждый параметр функции — для ввода, вывода или для обоих действий, давая чёткое представление о потоке данных и операциях, выполняемых функцией.
UT Analyzer анализирует код модульных тестов целевой библиотеки и формирует результат в виде json-файла. Обязательные параметры командной строки приведены ниже.
ut_analyzer --entry ${entry_path} --extern ${extern_path} --ut ${ut_type} --name ${lib_name} --public ${api_json_path} --out ${output_path}
Большинство параметров совпадает с параметрами target_analyzer. Обратите внимание, что entry_path должен указывать на AST/IR-файлы исполняемого файла модульных тестов, а не библиотеки.
Фреймворк, используемый целевым проектом; может быть tct, gtest или boost.
fuzz_generator генерирует фаззинг-драйверы, используя файлы отчётов target_anlayzer и ut_analyzer. Обязательные параметры командной строки приведены ниже.
fuzz_generator --src ${src_path} --target ${target_analyzer_report_path} --ut ${ut_analyzer_report_path} --public ${api_json_path} --out ${output_dir}
src_path — это путь к каталогу, в котором хранятся исходные коды модульных тестов. fuzz_generator копирует этот каталог и генерирует фаззинг-драйвер, изменяя скопированные файлы.
Остальные параметры совпадают с параметрами target_analyzer.
В этом разделе описываются функциональность и детали реализации сгенерированного фаззинг-драйвера, что важно для понимания того, как фаззинг-тестирование интегрируется с исходным кодом целевой библиотеки.
fuzz_entry.cc (автоматически создаваемый файл)
DEFINE_PROTO_FUZZER(const AutoFuzz::FuzzArgsProfile &autofuzz_mutation) {
... /* Values are assigned from autofuzz_mutation */
enterAutofuzz();
}
Эта функция является точкой входа для сгенерированного фаззинг-драйвера. Она принимает значения, сгенерированные фаззером, и присваивает их переменным, которые затем используются для вызова библиотечных функций. В конце функция вызывает enterAutofuzz(); для продолжения процесса фаззинг-тестирования.
Исходный код, определяющий целевой тестовый сценарий
#ifdef __cplusplus
extern "C" {
#endif
void enterAutofuzz() {
class AutofuzzTest : public ::Parser_TestArray_Test {
public:
void runTest() {
try {
SetUpTestCase();
} catch (std::exception &E) {}
try {
SetUp();
} catch (std::exception &E) {}
try {
TestBody();
} catch (std::exception &E) {}
try {
TearDown();
} catch (std::exception &E) {}
try {
TearDownTestCase();
} catch (std::exception &E) {}
}
};
AutofuzzTest Fuzzer;
Fuzzer.runTest();
}
#ifdef __cplusplus
}
#endif
В завершающей части исходного кода, определяющего целевой тестовый сценарий, UTopia внедряет функцию enterAutofuzz. Внутри этой функции объявляется класс AutofuzzTest, наследующий от ::Parser_TestArray_Test. Этот родительский класс определён фреймворком GoogleTest и соответствует конкретному тестовому сценарию, как показано ниже:
TEST(Parser, TestArray)
{
...
}
Метод runTest() выполняет тестовый сценарий независимо от GoogleTest, вызывая пять функций, которые GoogleTest обычно вызывает для каждого тестового сценария. Такой подход позволяет выполнять тесты напрямую, не полагаясь на фреймворк GoogleTest.
Фаззинг-драйверы создаются в ${output_dir}, переданном fuzz_generator в качестве параметра командной строки. Вы можете собрать их с помощью той же команды компилятора, которая используется для исполняемого файла модульных тестов. Обратите внимание, что нужно включить файлы fuzz_entry.cc, FuzzArgsProto.pb.cc, созданные fuzz_generator. Эти файлы можно найти в ${output_dir}.
python3 -m helper.make {library name}
python3 -m helper.build {library name}
Вы можете найти все результаты полного конвейера 'UTopia' в следующих двух каталогах:
exp/{library name}/output,result/test/{library name}