
Generación automatizada de fuzz drivers basada en UT
UTopia es una herramienta para generar automáticamente fuzz drivers a partir de pruebas unitarias.
UTopia permite a los desarrolladores realizar pruebas de fuzzing sin conocimientos especiales sobre cómo escribir fuzzers. Incluso los desarrolladores familiarizados con el fuzzing pueden ahorrar una cantidad significativa de tiempo generando fuzz drivers automáticamente.
UTopia es compatible con librerías de C/C++ que tienen pruebas unitarias con GoogleTest, Boost.Test o Tizen TCT.
Para ver errores encontrados por UTopia, visita la página Trofeo. También puedes ver algunos fuzzers basados en UTopia allí.
Para una configuración sencilla, proporcionamos una imagen de docker para ejecutar UTopia. Puedes construir UTopia y generar/ejecutar fuzzers dentro de un contenedor de docker.
Construye la imagen de docker con el siguiente comando.
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 está diseñado para funcionar mejor con LLVM versión 10. Además, se ha confirmado que supera las pruebas unitarias en LLVM versión 12. Por lo tanto, recomendamos usar LLVM 10, pero si lo deseas puedes probar LLVM versión 12.
UTopia depende de LLVM, Protobuf y GoogleTest. Puedes instalar las dependencias manualmente, pero recomendamos usar la imagen de docker proporcionada.
Después de clonar el repositorio, inicializa y actualiza todos los submódulos:
git submodule update --init --recursive
Para construir UTopia, sigue el proceso de cmake a continuación.
cd $UTOPIA_HOME_DIR
cmake -B build -S .
cmake --build build -j$(nproc)
Para algunos proyectos seleccionados, puedes usar el script auxiliar para ejecutar nuestra herramienta sin esfuerzo adicional. Por favor, consulta helper/README.md. Para otros proyectos, consulta el manual a continuación.
Target Analyzer analiza el código de la librería objetivo y genera un resultado como archivo json. Las opciones obligatorias de la línea de comandos son las siguientes.
target_analyzer --db ${builddb_path} --extern ${extern_path} --public ${api_json_path} --out ${output_path}
Build db es un archivo json que contiene las rutas de los archivos AST e IR del código de la librería objetivo. Su formato se ve a continuación.
{
"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"
}
La ruta del archivo bitcode de LLVM de una librería específica debe especificarse con la palabra clave "bc".
Solo aceptamos un archivo bitcode hasta ahora, por lo que puedes usar llvm-link para enlazar varios archivos de bitcode en un solo archivo de bitcode.
Las rutas de los archivos AST de una librería específica deben especificarse con la palabra clave "ast". Ten en cuenta que el archivo bitcode especificado y los archivos ast se generan a partir de los mismos códigos fuente para una librería específica.
Target Analyzer acepta el informe de target analyzer de otras librerías para obtener un resultado preciso. Esta ruta debe ser una ruta de directorio donde se almacenan otros informes, lo que significa que se permiten informes de más de una librería.
Nombres de las funciones API a analizar. Debe ser un archivo json con el formato siguiente.
{
"libcommon.a": [
"API1",
"API2",
"API3"
]
}
Puedes obtener la lista de API de una librería específica usando el siguiente comando.
nm --no-demangle --defined-only -g ${librarypath} | awk '$2=="T" {k=""; for(i=3;i<=NF;i++) k=k $i""; print k}'
La propiedad Direction es un parámetro esencial que utiliza el target analyzer. Indica si un parámetro se emplea para lectura (Dir_In), escritura (Dir_Out), o para lectura y escritura (Dir_In | Dir_Out) dentro de una función. El target analyzer define esta propiedad mediante un tipo de enumeración que comprende los siguientes elementos:
enum Dir {
Dir_NoOp = 0x000, // No operation
Dir_In = 0x100, // Input direction
Dir_Out = 0x010, // Output direction
Dir_Unidentified = 0x001 // Unidentified direction
};
Por ejemplo, en el fragmento de archivo JSON proporcionado:
{
"Direction": {
"BF_crypt(0)": 256,
"BF_crypt(1)": 272,
"BF_crypt(2)": 272,
"BF_decode(1)": 272
}
}
Esta notación ayuda a comprender cómo se utiliza cada parámetro dentro de una función, ya sea para entrada, salida o ambos, proporcionando información clara sobre el flujo de datos y las operaciones que realiza la función.
UT Analyzer analiza el código de las pruebas unitarias de una librería objetivo y genera un resultado como archivo json. Las opciones obligatorias de la línea de comandos son las siguientes.
ut_analyzer --entry ${entry_path} --extern ${extern_path} --ut ${ut_type} --name ${lib_name} --public ${api_json_path} --out ${output_path}
La mayoría de las opciones son las mismas que las de target_analyzer. Ten en cuenta que entry_path debe especificar los archivos AST/IR de un ejecutable de prueba unitaria, no de la librería.
Framework utilizado por el proyecto objetivo; puede ser tct, gtest o boost.
fuzz_generator genera fuzz drivers utilizando los archivos de informe de target_anlayzer y ut_analyzer. Las opciones obligatorias de la línea de comandos son las siguientes.
fuzz_generator --src ${src_path} --target ${target_analyzer_report_path} --ut ${ut_analyzer_report_path} --public ${api_json_path} --out ${output_dir}
src_path es la ruta del directorio donde se almacena el código fuente de las pruebas unitarias. fuzz_generator copia este directorio y genera el fuzz driver modificando esos archivos copiados.
Las demás opciones son las mismas que las opciones de target_analyzer.
Esta sección describe la funcionalidad y los detalles de implementación del fuzz driver generado, lo cual es crucial para comprender cómo se integran las pruebas de fuzzing con el código fuente objetivo.
fuzz_entry.cc (Archivo generado automáticamente)
DEFINE_PROTO_FUZZER(const AutoFuzz::FuzzArgsProfile &autofuzz_mutation) {
... /* Values are assigned from autofuzz_mutation */
enterAutofuzz();
}
Esta función sirve como punto de entrada para el fuzz driver generado. Toma valores generados por el fuzzer y los asigna a variables que posteriormente se utilizan para invocar las funciones de la librería. Finalmente, la función llama a enterAutofuzz(); para continuar con el proceso de pruebas de fuzzing.
Código fuente que define el caso de prueba objetivo
#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
En la parte final del código fuente que define el caso de prueba objetivo, UTopia inyecta la función enterAutofuzz. Dentro de esta función se declara la clase AutofuzzTest, que hereda de ::Parser_TestArray_Test. Esta clase padre está definida por el framework GoogleTest y es específica del caso de prueba que se está tratando, como se muestra a continuación:
TEST(Parser, TestArray)
{
...
}
El método runTest() ejecuta el caso de prueba de forma independiente de GoogleTest, invocando cinco funciones que GoogleTest normalmente llama para cada caso de prueba. Este enfoque permite la ejecución directa de la prueba sin depender del framework GoogleTest.
Los fuzz drivers se generan en ${output_dir}, pasado a fuzz_generator como opción de la línea de comandos.
Puedes compilarlos usando el mismo comando de compilador que para el ejecutable de prueba unitaria.
Ten en cuenta que debes incluir los archivos fuzz_entry.cc y FuzzArgsProto.pb.cc que son generados por fuzz_generator.
Puedes encontrar esos archivos en ${output_dir}.
python3 -m helper.make {library name}
python3 -m helper.build {library name}
Puedes encontrar todas las salidas de todo el pipeline de 'UTopia' en los siguientes dos directorios:
exp/{library name}/output,result/test/{library name}