
Esegue fuzzing sulle implementazioni di CPU generando input di test da proxy software, quindi li esegue su hardware reale per rilevare difetti di microarchitettura ed errata.
SiliFuzz è un sistema che trova difetti nelle CPU fuzzando proxy software, come simulatori o disassemblatori di CPU, ed esegue poi gli input di test accumulati (noti come corpus) su CPU reali su larga scala. SiliFuzz è un lavoro in corso; per i dettagli, fare riferimento all'articolo.
Il fuzzing è una tecnica per testare un target (un'applicazione o un'API) con un gran numero di input di test generati al volo. L'obiettivo è rendere questi input il più interessanti e diversificati possibile per innescare casi limite. In altre parole, il fuzzing mira a massimizzare la copertura complessiva del codice. La copertura del codice può avere significati diversi, ad esempio quali blocchi di base vengono eseguiti o quali percorsi vengono seguiti nel programma.
Ai fini di SiliFuzz, un proxy è qualsiasi sistema software o hardware che si comporta in modo simile ad alcuni aspetti della CPU target. Ad esempio, un emulatore di CPU o un disassemblatore. Un proxy è necessario quando non possiamo raccogliere direttamente informazioni sulla copertura dal target.
Applicando tecniche di fuzzing a un proxy, possiamo generare una serie di input di test (corpus) che producono un comportamento interessante nel proxy. La nostra ipotesi di base è che questo si traduca in un comportamento altrettanto interessante nel target. Per saperne di più, consulta la documentazione.
Una raccolta di input utilizzati per testare il target è nota come corpus.
Un corpus ragionevolmente grande contiene milioni di input e viene di solito suddiviso in più blocchi non sovrapposti chiamati shard.
Uno snapshot di SiliFuzz descrive una breve sequenza di istruzioni della CPU più uno stato iniziale dei registri e della memoria della CPU per eseguire deterministicamente quella sequenza. Uno snapshot tipico contiene meno di 100 byte di codice e viene eseguito in microsecondi, ma può essere arbitrariamente grande. Gli snapshot sono memorizzati come protocol buffer silifuzz.proto.Snapshot.
Gli snapshot vengono in genere creati a partire dagli input generati da un motore di fuzzing. Ai fini del test della CPU, questi input vengono filtrati per eliminare tutti gli snapshot non deterministici. Per saperne di più, consulta la documentazione.
Uno stato finale descrive il contenuto dei registri e della memoria che ci si aspetta esista al termine dell'esecuzione di uno Snapshot. Se lo snapshot viene eseguito in modo diverso su microarchitetture CPU differenti, avrà più stati finali attesi.
Uno Snap è una rappresentazione in memoria di uno Snapshot che può essere facilmente caricata ed eseguita dal Runner. Gli Snap vengono in genere caricati dal disco da un reading runner. Il formato su disco di uno Snap è essenzialmente lo stesso di quello in memoria, tranne per il fatto che i puntatori nativi sono sostituiti da offset. Vedi questo header per i dettagli. Questo formato è spesso chiamato rilocabile. Ogni Snap contiene esattamente uno stato finale atteso, ovvero gli Snap sono specifici per microarchitettura. Per saperne di più, consulta la documentazione.
Runner è un binario per testare un singolo core della CPU. Un runner consuma uno shard del corpus, esegue ripetutamente Snap casuali al suo interno e verifica che lo stato finale atteso venga raggiunto. Runner è un processo single-threaded.
L'orchestrator è un processo che gestisce più runner. In una configurazione tipica, l'orchestrator esegue continuamente un runner per ogni core logico della CPU e accumula e segnala eventuali errori prodotti dai singoli processi runner.
Vedi questo file per l'elenco delle microarchitetture supportate.
SiliFuzz gira su sistemi Linux x86_64 e aarch64. È stato testato con versioni del kernel Linux 5.x e 6.x. Non vi è alcuna garanzia che sia compatibile con versioni precedenti del kernel. L'ABI legacy vsyscall deve essere disattivata per evitare falsi positivi.
Un elenco non esaustivo di bug e difetti trovati da SiliFuzz.
Un bug logico è un comportamento non valido della CPU inerente a una particolare microarchitettura o stepping della CPU. SiliFuzz ha identificato i seguenti bug:
Un difetto (elettrico) è un comportamento non valido della CPU che si verifica solo su uno o più chip. SiliFuzz ha trovato i seguenti difetti che abbiamo descritto nell'articolo
git clone https://github.com/google/silifuzz.git && cd silifuzz
SILIFUZZ_SRC_DIR=`pwd`
./install_build_dependencies.sh # Currently, works for the latest Ubuntu only.
bazel build -c opt @silifuzz//tools:{snap_corpus_tool,fuzz_filter_tool,snap_tool,silifuzz_platform_id,simple_fix_tool_main} \
@silifuzz//runner:reading_runner_main_nolibc \
@silifuzz//orchestrator:silifuzz_orchestrator_main
SILIFUZZ_BIN_DIR=`pwd`/bazel-bin
cd "${SILIFUZZ_BIN_DIR}"
NOTA: Puoi usare un container Docker per evitare di sporcare il sistema host: docker run -it --tty --security-opt seccomp=unconfined --mount type=bind,source=${SILIFUZZ_SRC_DIR},target=/app ubuntu:noble /bin/bash -c "cd /app && ./install_build_dependencies.sh && bazel build ... && bazel test ..."
Per Bazel, usa i seguenti comandi.
cd "${SILIFUZZ_SRC_DIR}"
COV_FLAGS_FILE="$(bazel info output_base)/external/fuzztest+/centipede/clang-flags.txt"
bazel build -c opt --copt=-UNDEBUG --dynamic_mode=off \
--per_file_copt=unicorn/.*@$(xargs < "${COV_FLAGS_FILE}" |sed -e 's/,/\\,/g' -e 's/ /,/g') @//proxies:unicorn_x86_64
bazel build -c opt @fuzztest//centipede:centipede
mkdir -p /tmp/wd
# Fuzz the Unicorn proxy under Centipede 1000 times with parallelism of 30.
"${SILIFUZZ_BIN_DIR}/external/fuzztest+/centipede/centipede" \
--binary="${SILIFUZZ_BIN_DIR}/proxies/unicorn_x86_64" \
--workdir=/tmp/wd \
-j=30 --num_runs=1000