
Generazione automatizzata di driver fuzz basata su UT
UTopia è uno strumento per generare automaticamente fuzz driver a partire da unit test.
UTopia consente agli sviluppatori di eseguire fuzz testing senza conoscenze specifiche su come scrivere fuzzer. Anche gli sviluppatori esperti di fuzzing possono risparmiare tempo significativo generando fuzz driver automaticamente.
UTopia supporta librerie C/C++ che hanno unit test con GoogleTest, Boost.Test o Tizen TCT.
Per vedere i bug trovati da UTopia, visita la pagina Trofeo. Puoi anche vedere alcuni fuzzer basati su UTopia in quella pagina.
Per una configurazione semplice, forniamo un'immagine docker per eseguire UTopia. Puoi compilare UTopia e generare/eseguire fuzzer all'interno del container docker.
Costruisci l'immagine docker con il comando seguente.
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 è progettato per funzionare al meglio con LLVM versione 10. Inoltre, è stato confermato che supera gli unit test su LLVM versione 12. Pertanto ti consigliamo di usare LLVM 10, ma se vuoi puoi provare LLVM versione 12.
UTopia dipende da LLVM, Protobuf e GoogleTest. Puoi installare le dipendenze manualmente, ma consigliamo di usare l'immagine docker fornita.
Dopo aver clonato il repository, inizializza e aggiorna tutti i sottomoduli:
git submodule update --init --recursive
Per compilare UTopia, segui il processo cmake seguente.
cd $UTOPIA_HOME_DIR
cmake -B build -S .
cmake --build build -j$(nproc)
Per alcuni progetti selezionati, puoi usare uno script helper per eseguire il nostro strumento senza ulteriore sforzo. Fare riferimento a helper/README.md. Per altri progetti, controlla il manuale seguente.
Target Analyzer analizza il codice della libreria target e genera un risultato come file json. Le opzioni della riga di comando obbligatorie sono riportate di seguito.
target_analyzer --db ${builddb_path} --extern ${extern_path} --public ${api_json_path} --out ${output_path}
Build db è un file json che contiene i percorsi dei file AST e IR del codice della libreria target. Il suo formato è simile al seguente.
{
"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"
}
Il percorso del file bitcode LLVM di una specifica libreria deve essere specificato usando la parola chiave "bc".
Accettiamo un solo file bitcode finora, quindi puoi usare llvm-link per collegare più file bitcode in un unico file bitcode.
I percorsi dei file AST di una specifica libreria devono essere specificati usando la parola chiave "ast". Nota che il file bitcode specificato e i file ast sono generati dagli stessi codici sorgente per una specifica libreria.
Target Analyzer accetta report di target analyzer di altre librerie per un risultato accurato. Questo percorso deve essere un percorso di directory in cui sono memorizzati altri report, il che significa che sono consentiti più report di librerie.
Nomi delle funzioni API da analizzare. Deve essere un file json formattato come segue.
{
"libcommon.a": [
"API1",
"API2",
"API3"
]
}
Puoi ottenere l'elenco delle API da una specifica libreria usando il comando seguente.
nm --no-demangle --defined-only -g ${librarypath} | awk '$2=="T" {k=""; for(i=3;i<=NF;i++) k=k $i""; print k}'
La proprietà Direction è un parametro essenziale che il target analyzer utilizza. Indica se un parametro è impiegato per la lettura (Dir_In), per la scrittura (Dir_Out) o sia per la lettura che per la scrittura (Dir_In | Dir_Out) all'interno di una funzione. Il target analyzer definisce questa proprietà tramite un tipo di enumerazione che comprende i seguenti elementi:
enum Dir {
Dir_NoOp = 0x000, // No operation
Dir_In = 0x100, // Input direction
Dir_Out = 0x010, // Output direction
Dir_Unidentified = 0x001 // Unidentified direction
};
Ad esempio, nel frammento di file JSON fornito:
{
"Direction": {
"BF_crypt(0)": 256,
"BF_crypt(1)": 272,
"BF_crypt(2)": 272,
"BF_decode(1)": 272
}
}
Questa notazione aiuta a capire come viene usato ciascun parametro all'interno di una funzione, se per input, output o entrambi, fornendo chiare informazioni sul flusso di dati e sulle operazioni eseguite dalla funzione.
UT Analyzer analizza il codice degli unit test per una libreria target e genera un risultato come file json. Le opzioni della riga di comando obbligatorie sono riportate di seguito.
ut_analyzer --entry ${entry_path} --extern ${extern_path} --ut ${ut_type} --name ${lib_name} --public ${api_json_path} --out ${output_path}
La maggior parte delle opzioni è la stessa del target_analyzer. Nota che entry_path deve specificare i file AST/IR per un eseguibile di unit test, non per la libreria.
Framework utilizzato dal progetto target, può essere tct, gtest o boost.
fuzz_generator genera fuzz driver usando i file di report di target_anlayzer e ut_analyzer. Le opzioni della riga di comando obbligatorie sono riportate di seguito.
fuzz_generator --src ${src_path} --target ${target_analyzer_report_path} --ut ${ut_analyzer_report_path} --public ${api_json_path} --out ${output_dir}
src_path è il percorso della directory in cui è memorizzato il codice sorgente degli unit test. fuzz_generator copia questa directory e genera il fuzz driver modificando questi file copiati.
Le altre opzioni sono le stesse del target_analyzer.
Questa sezione descrive le funzionalità e i dettagli implementativi del fuzz driver generato, che è fondamentale per capire come il fuzz testing viene integrato con il codice sorgente target.
fuzz_entry.cc (File Auto-Generato)
DEFINE_PROTO_FUZZER(const AutoFuzz::FuzzArgsProfile &autofuzz_mutation) {
... /* Values are assigned from autofuzz_mutation */
enterAutofuzz();
}
Questa funzione funge da punto di ingresso per il fuzz driver generato. Prende i valori generati dal fuzzer e li assegna a variabili che vengono successivamente utilizzate per invocare le funzioni della libreria. Infine, la funzione chiama enterAutofuzz(); per proseguire con il processo di fuzz testing.
Codice Sorgente che Definisce il Test Case Target
#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
Nella parte finale del codice sorgente che definisce il test case target, UTopia inietta la funzione enterAutofuzz. All'interno di questa funzione, viene dichiarata la classe AutofuzzTest, che eredita da ::Parser_TestArray_Test. Questa classe genitore è definita dal framework GoogleTest ed è specifica per il test case in questione, come mostrato di seguito:
TEST(Parser, TestArray)
{
...
}
Il metodo runTest() esegue il test case indipendentemente da GoogleTest, invocando cinque funzioni che GoogleTest chiama tipicamente per ogni test case. Questo approccio consente l'esecuzione diretta del test senza dipendere dal framework GoogleTest.
I fuzz driver vengono generati in ${output_dir} passato a fuzz_generator come opzione della riga di comando.
Puoi compilarli usando lo stesso comando del compilatore per l'eseguibile degli unit test.
Nota che devi includere i file fuzz_entry.cc, FuzzArgsProto.pb.cc generati da fuzz_generator.
Puoi trovare questi file in ${output_dir}.
python3 -m helper.make {library name}
python3 -m helper.build {library name}
Puoi trovare tutti gli output dell'intera pipeline di 'UTopia' nelle seguenti due directory:
exp/{library name}/output,result/test/{library name}