testsuite/fuzzer/README.md
This directory contains tools and scripts essential for fuzz testing on Aptos Core. Fuzz targets run continuously on daily versions of main on Google's OSS-Fuzz infrastructure.
fuzz.shfuzz.sh is the main script to perform common fuzzing-related operations.
The script includes several functions to manage and execute fuzz tests:
add: Add specified fuzz target.
./fuzz.sh add <fuzz_target_name>
block-builder: Run rust utility to build fuzzers.
./fuzz.sh block-builder <utility> [args]
block-builder-recursive: Runs block-builder on all Move.toml files in a directory.
./fuzz.sh block-builder-recursive <search_directory> <destination_directory>
build: Build specified fuzz targets or all targets.
./fuzz.sh build <fuzz_target|all> [target_dir]
build-oss-fuzz: Build fuzz targets specifically for OSS-Fuzz.
./fuzz.sh build-oss-fuzz <target_dir>
clean-coverage: Clean coverage artifacts for a specific target or all targets.
./fuzz.sh clean-coverage <fuzz_target|all>
cmin: Distillate corpora
./fuzz.sh cmin <fuzz_target> [corpora_directory]
coverage: Generates coverage report in HTML format
./fuzz.sh coverage <fuzz_target>
rustup +nightly-2024-04-06 component add llvm-tools-preview
debug: Run fuzzer with GDB and pass test_case as input
./fuzz.sh debug <fuzz_target> <test_case>
flamegraph: Generates flamegraph report (might requires addition setups on the os)
./fuzz.sh flamegraph <fuzz_target> <test_case>
list: List all existing fuzz targets.
./fuzz.sh list
monitor-coverage: Monitors coverage for a fuzz target, regenerating when the corpus changes.
./fuzz.sh monitor-coverage <fuzz_target>
run: Run a specific fuzz target, optionally with a testcase.
./fuzz.sh run <fuzz_target> [testcase]
test: Test all fuzz targets with predefined parameters.
./fuzz.sh test
tmin: Minimize a crashing input for a target.
./fuzz.sh tmin <fuzz_target> <crashing_input>
To set up a fuzz harness in Aptos-core using cargo-fuzz:
Run the following command to initialize the fuzzing target. This creates and edits all the necessary files.
./fuzz.sh add fuzz_target_name
The basic structure of a fuzz target in Rust using cargo-fuzz is:
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
// Code to handle the fuzzer input and test the desired functionality
});
In example above, the fuzzing engine provides a random slice of bytes. Alternatively, it can provide struct-aware data by leveraging the Arbitrary trait, defined in the omonimous crate. The easiest way to implement the Arbitrary trait is to derive it (via the derive_arbitrary feature.) For example:
#![no_main]
use arbitrary::Arbitrary;
use libfuzzer_sys::fuzz_target;
#[derive(Arbitrary)]
struct ComplexData {}
fuzz_target!(|data: ComplexData| {
// Code to handle the fuzzer input and test the desired functionality
});
Note that Arbitrary must be implemented (or derived) for all types used in the ComplexData structure.
The fuzz_target! macro receives data from the fuzzer. Implement logic to convert the fuzzer input into a format that the targeted function or module can process. Check existing fuzz targets for examples.
Create a .zip archive containing your fuzzer's corpus and name it according to the following format: [fuzzer_name]_seed_corpus.zip (e.g., move_aptosvm_publish_and_run_seed_corpus.zip). Follow these steps for hosting and integrating the archive:
Upload to Public Hosting: If you choose Google Drive, ensure the archive is publicly accessible via a shared link.
(GDrive Only) Modify the URL: Replace FILEID in the URL template with your file's ID. The template URL is:
https://docs.google.com/uc?export=download&id=FILEID
Update fuzz.sh: Insert the modified URL into the CORPUS_ZIPS array within the "fuzz.sh" script.
When building in the OSS-Fuzz environment, fuzz.sh will place the corpus archive correctly alongside your fuzzer's binary. OSS-Fuzz then selects the proper archive, using its contents to feed the fuzzer.
Some fuzzers operate better if a good initial corpus is provided. In order to generate the corpus, utilities are available via ./fuzz.sh block-builder. Once a corpus is obtained, to feed it to fuzzers running on OSS-Fuzz, building a ZIP archive with a specific name is required: $FUZZERNAME_seed_corpus.zip. Upload it to a publicly accessible cloud, e.g., GCP Bucket or S3; avoid GDrive. Obtain a public link and add it to the CORPUS_ZIPS array in fuzz.sh. It will automatically be downloaded and used inside Google's infrastructure.
./fuzz.sh block-builder generate_runnable_state /tmp/modules.csv /tmp/Modules
The CSV file is structured as follows:
You can generate a test case from any valid Move project (arguments to function calls might need attention TODO). It's helpful for testing new functionalities or increasing coverage completeness. For native functions, please follow the structure in the data folder.
Create an entry function, which may accept a signer or no parameters. Generic T functions are not allowed as entry.
The first argument is the project, and the second one is the target directory.
./fuzz.sh block-builder generate_runnable_state_from_project data/0x1/string/generic fuzz/corpus/move_aptosvm_publish_and_run
Verify your testcase runs as expected by appending
DEBUG=1while calling the fuzzer and using the newly generated test case as the second parameter.
Use ./fuzz.sh block-builder generate_runnable_states_recursive data/0x1/ fuzz/corpus/move_aptosvm_publish_and_run to compile all the modules under a specific directory.
The following steps apply to wathever seed we might want to make available, remember to add the public link at the begin of fuzz.sh.
gcloud auth logingcloud storage cp gs://aptos-core-corpora/move_aptosvm_publish_and_run_seed_corpus.zip move_aptosvm_publish_and_run_seed_corpus.zipunzip move_aptosvm_publish_and_run_seed_corpus.zip -d move_aptosvm_publish_and_run_seed_corpus./fuzz.sh block-builder generate_runnable_states_recursive data/0x1/ move_aptosvm_publish_and_run_seed_corpuszip -r move_aptosvm_publish_and_run_seed_corpus.zip move_aptosvm_publish_and_run_seed_corpusgsutil storage cp move_aptosvm_publish_and_run_seed_corpus.zip gs://aptos-core-corpora/move_aptosvm_publish_and_run_seed_corpus.zipgsutil storage acl ch -u AllUsers:R gs://aptos-core-corpora/move_aptosvm_publish_and_run_seed_corpus.zipFlamegraph and GDB are integrated into fuzz.sh for advanced metrics and debugging. A more rudimentary option is also available: since we have symbolized binaries, we can directly use the stack trace produced by the fuzzer. However, for INVARIANT_VIOLATIONS, the stack trace is incorrect. To obtain the correct stack trace, you can use the following command:
DEBUG_VM_STATUS=<status_reported_by_the_fuzzer> ./fuzz.sh run <fuzzer_target> <test_case>
This command is selective, so only the specified, comma-separated statuses will trigger the panic in PartialVMError.
Contributions to enhance the fuzz.sh script and the fuzz testing suite are welcome.