
Génération automatisée de pilotes de fuzz basée sur UT
UTopia est un outil permettant de générer automatiquement des fuzz drivers à partir de tests unitaires.
UTopia permet aux développeurs d'effectuer des tests de fuzzing sans connaissances particulières sur l'écriture de fuzzers. Même les développeurs familiers avec le fuzzing peuvent gagner un temps considérable en générant automatiquement des fuzz drivers.
UTopia prend en charge les bibliothèques C/C++ qui disposent de tests unitaires avec GoogleTest, Boost.Test ou Tizen TCT.
Pour voir les bogues trouvés par UTopia, consultez la page Trophy. Vous pouvez également y voir quelques fuzzers basés sur UTopia.
Pour une mise en place simple, nous fournissons une image docker pour exécuter UTopia. Vous pouvez compiler UTopia et générer/exécuter des fuzzers dans un conteneur docker.
Construisez l'image docker avec la commande ci-dessous.
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 conçu pour fonctionner au mieux avec la version 10 de LLVM. De plus, il a été confirmé qu'il passe les tests unitaires avec la version 12 de LLVM. Nous vous recommandons donc d'utiliser LLVM 10, mais si vous le souhaitez, vous pouvez essayer la version 12 de LLVM.
UTopia dépend de LLVM, Protobuf et GoogleTest. Vous pouvez installer les dépendances manuellement, mais nous vous recommandons d'utiliser l'image docker fournie.
Après avoir cloné le dépôt, initialisez et mettez à jour tous les sous-modules :
git submodule update --init --recursive
Pour compiler UTopia, suivez le processus cmake ci-dessous.
cd $UTOPIA_HOME_DIR
cmake -B build -S .
cmake --build build -j$(nproc)
Pour certains projets sélectionnés, vous pouvez utiliser un script d'assistance pour exécuter notre outil sans effort supplémentaire. Veuillez consulter helper/README.md. Pour les autres projets, veuillez consulter le manuel suivant.
Target Analyzer analyse le code de la bibliothèque cible et génère un résultat sous forme de fichier json. Les options de ligne de commande obligatoires sont les suivantes.
target_analyzer --db ${builddb_path} --extern ${extern_path} --public ${api_json_path} --out ${output_path}
Build db est un fichier json qui contient les chemins des fichiers AST et IR du code de la bibliothèque cible. Son format ressemble à ce qui suit.
{
"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"
}
Le chemin du fichier bitcode LLVM d'une bibliothèque spécifique doit être spécifié à l'aide du mot-clé "bc".
Nous n'acceptons qu'un seul fichier bitcode pour le moment, vous pouvez donc utiliser llvm-link pour lier plusieurs fichiers bitcode en un seul fichier bitcode.
Les chemins des fichiers AST d'une bibliothèque spécifique doivent être spécifiés à l'aide du mot-clé "ast". Notez qu'un fichier bitcode spécifié et les fichiers AST sont générés à partir des mêmes codes sources pour une bibliothèque spécifique.
Target Analyzer accepte les rapports target analyzer d'autres bibliothèques pour un résultat précis. Ce chemin doit être un chemin de répertoire où les autres rapports sont stockés, ce qui signifie que plusieurs rapports de bibliothèques sont autorisés.
Noms des fonctions API à analyser. Il doit s'agir d'un fichier json formaté comme ci-dessous.
{
"libcommon.a": [
"API1",
"API2",
"API3"
]
}
Vous pouvez obtenir la liste des API d'une bibliothèque spécifique à l'aide de la commande ci-dessous.
nm --no-demangle --defined-only -g ${librarypath} | awk '$2=="T" {k=""; for(i=3;i<=NF;i++) k=k $i""; print k}'
La propriété Direction est un paramètre essentiel utilisé par target analyzer. Elle indique si un paramètre est utilisé pour la lecture (Dir_In), l'écriture (Dir_Out), ou la lecture et l'écriture (Dir_In | Dir_Out) dans une fonction. target analyzer définit cette propriété à travers un type d'énumération comprenant les éléments suivants :
enum Dir {
Dir_NoOp = 0x000, // No operation
Dir_In = 0x100, // Input direction
Dir_Out = 0x010, // Output direction
Dir_Unidentified = 0x001 // Unidentified direction
};
Par exemple, dans l'extrait de fichier JSON fourni :
{
"Direction": {
"BF_crypt(0)": 256,
"BF_crypt(1)": 272,
"BF_crypt(2)": 272,
"BF_decode(1)": 272
}
}
Cette notation aide à comprendre comment chaque paramètre d'une fonction est utilisé, que ce soit pour l'entrée, la sortie ou les deux, fournissant ainsi des informations claires sur le flux de données et les opérations effectuées par la fonction.
UT Analyzer analyse le code de test unitaire d'une bibliothèque cible et génère un résultat sous forme de fichier json. Les options de ligne de commande obligatoires sont les suivantes.
ut_analyzer --entry ${entry_path} --extern ${extern_path} --ut ${ut_type} --name ${lib_name} --public ${api_json_path} --out ${output_path}
La plupart des options sont identiques à celles de target_analyzer. Notez que entry_path doit spécifier les fichiers AST/IR d'un exécutable de test unitaire, et non de la bibliothèque.
Framework utilisé par le projet cible, peut être tct, gtest ou boost.
fuzz_generator génère des fuzz drivers à l'aide des fichiers de rapport de target_anlayzer et de ut_analyzer. Les options de ligne de commande obligatoires sont les suivantes.
fuzz_generator --src ${src_path} --target ${target_analyzer_report_path} --ut ${ut_analyzer_report_path} --public ${api_json_path} --out ${output_dir}
src_path est le chemin du répertoire où le code source du test unitaire est stocké. fuzz_generator copie ce répertoire et génère un fuzz driver en modifiant ces fichiers copiés.
Les autres options sont identiques à celles de target_analyzer.
Cette section décrit les fonctionnalités et les détails d'implémentation du fuzz driver généré, ce qui est essentiel pour comprendre comment le fuzzing est intégré au code source cible.
fuzz_entry.cc (fichier auto-généré)
DEFINE_PROTO_FUZZER(const AutoFuzz::FuzzArgsProfile &autofuzz_mutation) {
... /* Values are assigned from autofuzz_mutation */
enterAutofuzz();
}
Cette fonction sert de point d'entrée pour le fuzz driver généré. Elle prend les valeurs générées par le fuzzer et les assigne à des variables qui sont ensuite utilisées pour invoquer les fonctions de la bibliothèque. Enfin, la fonction appelle enterAutofuzz(); pour poursuivre le processus de fuzzing.
Code source définissant le cas de test cible
#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
Dans la dernière partie du code source qui définit le cas de test cible, UTopia injecte la fonction enterAutofuzz. Dans cette fonction, la classe AutofuzzTest est déclarée, héritant de ::Parser_TestArray_Test. Cette classe parente est définie par le framework GoogleTest et est spécifique au cas de test traité, comme illustré ci-dessous :
TEST(Parser, TestArray)
{
...
}
La méthode runTest() exécute le cas de test indépendamment de GoogleTest, en invoquant cinq fonctions que GoogleTest appelle généralement pour chaque cas de test. Cette approche permet une exécution directe des tests sans dépendre du framework GoogleTest.
Les fuzz drivers sont générés dans ${output_dir} passé à fuzz_generator comme option de ligne de commande.
Vous pouvez les compiler en utilisant la même commande compilateur que pour l'exécutable de test unitaire.
Notez que vous devez inclure les fichiers fuzz_entry.cc, FuzzArgsProto.pb.cc qui sont générés par fuzz_generator.
Vous pouvez trouver ces fichiers dans ${output_dir}.
python3 -m helper.make {library name}
python3 -m helper.build {library name}
Vous pouvez trouver toutes les sorties de l'ensemble du pipeline d'UTopia dans les deux répertoires suivants :
exp/{library name}/output,result/test/{library name}