From 4d8b566a872b98b58daaa061289bf145de57cf95 Mon Sep 17 00:00:00 2001 From: Max Ammann Date: Wed, 21 Dec 2022 10:41:58 +0100 Subject: [PATCH] [Windows] Add libfuzzer example for windows with ASAN (#934) * Add libfuzzer example for window with ASAN * Fix formatting * Add link * Fix cpp format * Skip windows fuzzers * Fix format * Fix testing fuzzer * Fix taks name Co-authored-by: Dongjia "toka" Zhang Co-authored-by: Andrea Fioraldi Co-authored-by: Dominik Maier --- fuzzers/libfuzzer_windows_asan/.gitignore | 1 + fuzzers/libfuzzer_windows_asan/Cargo.toml | 26 +++ fuzzers/libfuzzer_windows_asan/Makefile.toml | 98 +++++++++ fuzzers/libfuzzer_windows_asan/README.md | 49 +++++ .../libfuzzer_windows_asan/corpus/hello_world | 1 + fuzzers/libfuzzer_windows_asan/harness.cpp | 16 ++ .../src/bin/libafl_cc.rs | 43 ++++ .../src/bin/libafl_cxx.rs | 5 + fuzzers/libfuzzer_windows_asan/src/lib.rs | 187 ++++++++++++++++++ libafl_cc/src/common-llvm.h | 2 +- libafl_targets/src/libfuzzer.c | 9 + 11 files changed, 436 insertions(+), 1 deletion(-) create mode 100644 fuzzers/libfuzzer_windows_asan/.gitignore create mode 100644 fuzzers/libfuzzer_windows_asan/Cargo.toml create mode 100644 fuzzers/libfuzzer_windows_asan/Makefile.toml create mode 100644 fuzzers/libfuzzer_windows_asan/README.md create mode 100644 fuzzers/libfuzzer_windows_asan/corpus/hello_world create mode 100644 fuzzers/libfuzzer_windows_asan/harness.cpp create mode 100644 fuzzers/libfuzzer_windows_asan/src/bin/libafl_cc.rs create mode 100644 fuzzers/libfuzzer_windows_asan/src/bin/libafl_cxx.rs create mode 100644 fuzzers/libfuzzer_windows_asan/src/lib.rs diff --git a/fuzzers/libfuzzer_windows_asan/.gitignore b/fuzzers/libfuzzer_windows_asan/.gitignore new file mode 100644 index 0000000000..afa2c0a386 --- /dev/null +++ b/fuzzers/libfuzzer_windows_asan/.gitignore @@ -0,0 +1 @@ +libfuzzer_windows_asan* \ No newline at end of file diff --git a/fuzzers/libfuzzer_windows_asan/Cargo.toml b/fuzzers/libfuzzer_windows_asan/Cargo.toml new file mode 100644 index 0000000000..8a5810ae43 --- /dev/null +++ b/fuzzers/libfuzzer_windows_asan/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "libfuzzer_windows_asan" +version = "0.8.2" +authors = ["Max Ammann "] +edition = "2021" +categories = ["development-tools::testing"] + +[features] + +[profile.release] +lto = true +codegen-units = 1 +opt-level = 3 +debug = true + +[dependencies] +libafl = { path = "../../libafl/" } +libafl_targets = { path = "../../libafl_targets/", features = ["libfuzzer", "sancov_pcguard_edges"] } +libafl_cc = { path = "../../libafl_cc/" } + +[build-dependencies] +cc = { version = "1.0" } + +[lib] +name = "libfuzzer_windows_asan" +crate-type = ["staticlib"] diff --git a/fuzzers/libfuzzer_windows_asan/Makefile.toml b/fuzzers/libfuzzer_windows_asan/Makefile.toml new file mode 100644 index 0000000000..3f3df2877e --- /dev/null +++ b/fuzzers/libfuzzer_windows_asan/Makefile.toml @@ -0,0 +1,98 @@ +# Variables +[env] +FUZZER_NAME='libfuzzer_windows_asan' +CARGO_TARGET_DIR = { value = "./target", condition = { env_not_set = ["CARGO_TARGET_DIR"] } } + +[tasks.unsupported] +script_runner="@shell" +script=''' +echo "Cargo-make not integrated yet on this" +''' + +# Compilers +[tasks.cxx] +linux_alias = "unsupported" +mac_alias = "unsupported" +windows_alias = "cxx_unix" + +[tasks.cxx_unix] +command = "cargo" +args = ["build" , "--release"] + +[tasks.cc] +linux_alias = "cc_unix" +mac_alias = "cc_unix" +windows_alias = "cc_unix" + +[tasks.cc_unix] +command = "cargo" +args = ["build" , "--release"] + +[tasks.crash_cxx] +linux_alias = "unsupported" +mac_alias = "unsupported" +windows_alias = "unsupported" + +[tasks.crash_cc] +linux_alias = "unsupported" +mac_alias = "unsupported" +windows_alias = "unsupported" + +# Library +[tasks.lib] +linux_alias = "unsupported" +mac_alias = "unsupported" +windows_alias = "lib_unix" + +[tasks.lib_unix] +dependencies = [ "cxx", "cc" ] + +# Harness +[tasks.fuzzer] +linux_alias = "unsupported" +mac_alias = "unsupported" +windows_alias = "fuzzer_windows" + +[tasks.fuzzer_windows] +command = "${CARGO_TARGET_DIR}/release/libafl_cxx" +args = ["./harness.cpp", "-o", "${FUZZER_NAME}.exe"] +dependencies = [ "lib", "cxx", "cc" ] + +# Run the fuzzer +[tasks.run] +linux_alias = "unsupported" +mac_alias = "unsupported" +windows_alias = "run_windows" # TODO + +[tasks.run_windows] +script_runner = "@shell" +script=''' +''' +dependencies = [ "fuzzer" ] + +# Test +[tasks.test] +linux_alias = "unsupported" +mac_alias = "unsupported" +windows_alias = "test_windows" # TODO + +[tasks.test_windows] +script_runner = "@shell" +script=''' +''' +dependencies = [ "fuzzer" ] + +# Clean up +[tasks.clean] +linux_alias = "unsupported" +mac_alias = "unsupported" +windows_alias = "clean_windows" + +[tasks.clean_windows] +# Disable default `clean` definition +clear = true +script_runner="@shell" +script=''' +del ./${FUZZER_NAME} +cargo clean +''' \ No newline at end of file diff --git a/fuzzers/libfuzzer_windows_asan/README.md b/fuzzers/libfuzzer_windows_asan/README.md new file mode 100644 index 0000000000..357510c8d5 --- /dev/null +++ b/fuzzers/libfuzzer_windows_asan/README.md @@ -0,0 +1,49 @@ +# LibFuzzer Example for Windows with ASAN + +This folder contains an example fuzzer for Windows which also uses ASAN. + +We are initializing LibAFL to be compatible with ASAN. + +## Setup + +We are currently using Clang on Windows. Make sure to install Clang through the Visual Studio installer. + +We recommend using Powershell and enabling the Visual Studio environment using this script: + +```powershell +Push-Location "C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\" +& "C:\\Windows\System32\cmd.exe" /c "vcvars64.bat & set" | +ForEach-Object { + if ($_ -match "=") { + $v = $_.split("=", 2); set-item -force -path "ENV:\$($v[0])" -value "$($v[1])" + } +} +Pop-Location +Write-Host "`nVisual Studio 2022 Command Prompt variables set." -ForegroundColor Yellow +```` + +After that clang should be available in the PATH. + +## Build + +To build the fuzzer and link against the `harness.cpp` in this example run: + +``` +cargo make fuzzer +``` + +## Running + +``` +.\libfuzzer_windows_asan.exe +``` + +## Note on MSVC + +The MSVC compiler (`cl.exe`) will work in the future. Currently, it is blocked because of a [bug](https://developercommunity.visualstudio.com/t/__sanitizer_cov_trace_pc_guard_init-neve/10218995) with coverage. + +### Note on ASAN + +Using ASAN on Windows with MSVC is not trivial as of 2022. Depending on the harness and fuzzing target, the required compilation flags differ. Most notably, the usage of `/MT` and `/MD` for the CRT is important. All compilation artifacts should use the same config for the CRT (either all `/MT` or all `/MD`). [Rust uses as of 2022](https://rust-lang.github.io/rfcs/1721-crt-static.html) `/MD` as default. So compile everything with `/MD`. + +Depending on the linking mode different ASAN libraries get linked. Definitely read [this](https://devblogs.microsoft.com/cppblog/addresssanitizer-asan-for-windows-with-msvc/) blog post by Microsoft. diff --git a/fuzzers/libfuzzer_windows_asan/corpus/hello_world b/fuzzers/libfuzzer_windows_asan/corpus/hello_world new file mode 100644 index 0000000000..557db03de9 --- /dev/null +++ b/fuzzers/libfuzzer_windows_asan/corpus/hello_world @@ -0,0 +1 @@ +Hello World diff --git a/fuzzers/libfuzzer_windows_asan/harness.cpp b/fuzzers/libfuzzer_windows_asan/harness.cpp new file mode 100644 index 0000000000..4c4e3b0f72 --- /dev/null +++ b/fuzzers/libfuzzer_windows_asan/harness.cpp @@ -0,0 +1,16 @@ +#include +#include +#include + +void asan_crash() { + int *array = new int[100]; + delete[] array; + array[5] += 1; + fprintf(stdout, "%d\n", array[5]); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + // abort(); + asan_crash(); + return 0; +} diff --git a/fuzzers/libfuzzer_windows_asan/src/bin/libafl_cc.rs b/fuzzers/libfuzzer_windows_asan/src/bin/libafl_cc.rs new file mode 100644 index 0000000000..64c9536d5c --- /dev/null +++ b/fuzzers/libfuzzer_windows_asan/src/bin/libafl_cc.rs @@ -0,0 +1,43 @@ +use std::env; + +use libafl_cc::{ClangWrapper, CompilerWrapper}; + +pub fn main() { + let args: Vec = env::args().collect(); + if args.len() > 1 { + let mut dir = env::current_exe().unwrap(); + let wrapper_name = dir + .file_name() + .unwrap() + .to_str() + .unwrap() + .replace(".exe", ""); + + let is_cpp = match wrapper_name[wrapper_name.len()-2..].to_lowercase().as_str() { + "cc" => false, + "++" | "pp" | "xx" => true, + _ => panic!("Could not figure out if c or c++ warpper was called. Expected {:?} to end with c or cxx", dir), + }; + + dir.pop(); + + let mut cc = ClangWrapper::new(); + if let Some(code) = cc + .cpp(is_cpp) + // silence the compiler wrapper output, needed for some configure scripts. + .silence(true) + .parse_args(&args) + .expect("Failed to parse the command line") + .link_staticlib(&dir, "libfuzzer_windows_asan") + .add_arg("-lOleAut32.lib") + .add_arg("-fsanitize-coverage=trace-pc-guard") + .add_arg("-fsanitize=address") + .run() + .expect("Failed to run the wrapped compiler") + { + std::process::exit(code); + } + } else { + panic!("LibAFL CC: No Arguments given"); + } +} diff --git a/fuzzers/libfuzzer_windows_asan/src/bin/libafl_cxx.rs b/fuzzers/libfuzzer_windows_asan/src/bin/libafl_cxx.rs new file mode 100644 index 0000000000..ce786239b0 --- /dev/null +++ b/fuzzers/libfuzzer_windows_asan/src/bin/libafl_cxx.rs @@ -0,0 +1,5 @@ +pub mod libafl_cc; + +fn main() { + libafl_cc::main() +} diff --git a/fuzzers/libfuzzer_windows_asan/src/lib.rs b/fuzzers/libfuzzer_windows_asan/src/lib.rs new file mode 100644 index 0000000000..f9877d3909 --- /dev/null +++ b/fuzzers/libfuzzer_windows_asan/src/lib.rs @@ -0,0 +1,187 @@ +use core::time::Duration; +use std::{env, path::PathBuf}; + +use libafl::{ + bolts::{ + current_nanos, + rands::StdRand, + tuples::{tuple_list, Merge}, + AsSlice, + }, + corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, + events::{setup_restarting_mgr_std, EventConfig, EventRestarter}, + executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor}, + feedback_or, feedback_or_fast, + feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::{BytesInput, HasTargetBytes}, + monitors::MultiMonitor, + mutators::{ + scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator}, + token_mutations::Tokens, + }, + observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, + schedulers::{ + powersched::PowerSchedule, IndexesLenTimeMinimizerScheduler, StdWeightedScheduler, + }, + stages::{calibrate::CalibrationStage, power::StdPowerMutationalStage}, + state::{HasCorpus, HasMetadata, StdState}, + Error, +}; +use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input, EDGES_MAP, MAX_EDGES_NUM}; + +#[no_mangle] +pub fn libafl_main() { + println!( + "Workdir: {:?}", + env::current_dir().unwrap().to_string_lossy().to_string() + ); + fuzz( + &[PathBuf::from("./corpus")], + PathBuf::from("./crashes"), + 1337, + ) + .expect("An error occurred while fuzzing"); +} + +/// The actual fuzzer +#[cfg(not(test))] +fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { + // 'While the stats are state, they are usually used in the broker - which is likely never restarted + let monitor = MultiMonitor::new(|s| println!("{}", s)); + + // The restarting state will spawn the same process again as child, then restarted it each time it crashes. + let (state, mut restarting_mgr) = + match setup_restarting_mgr_std(monitor, broker_port, EventConfig::AlwaysUnique) { + Ok(res) => res, + Err(err) => match err { + Error::ShuttingDown => { + return Ok(()); + } + _ => { + panic!("Failed to setup the restarter: {}", err); + } + }, + }; + + // Create an observation channel using the coverage map + let edges = unsafe { &mut EDGES_MAP[0..MAX_EDGES_NUM] }; + let edges_observer = HitcountsMapObserver::new(StdMapObserver::new("edges", edges)); + + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); + + let map_feedback = MaxMapFeedback::new_tracking(&edges_observer, true, false); + + let calibration = CalibrationStage::new(&map_feedback); + + // Feedback to rate the interestingness of an input + // This one is composed by two Feedbacks in OR + let mut feedback = feedback_or!( + // New maximization map feedback linked to the edges observer and the feedback state + map_feedback, + // Time feedback, this one does not need a feedback state + TimeFeedback::new_with_observer(&time_observer) + ); + + // A feedback to choose if an input is a solution or not + let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + + // If not restarting, create a State from scratch + let mut state = state.unwrap_or_else(|| { + StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + InMemoryCorpus::new(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(objective_dir).unwrap(), + // States of the feedbacks. + // The feedbacks can report the data that should persist in the State. + &mut feedback, + // Same for objective feedbacks + &mut objective, + ) + .unwrap() + }); + + println!("We're a client, let's fuzz :)"); + + // Setup a basic mutator with a mutational stage + + let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations())); + + let power = StdPowerMutationalStage::new(mutator, &edges_observer); + + let mut stages = tuple_list!(calibration, power); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerScheduler::new(StdWeightedScheduler::with_schedule( + PowerSchedule::FAST, + )); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + // The wrapped harness function, calling out to the LLVM-style harness + let mut harness = |input: &BytesInput| { + let target = input.target_bytes(); + let buf = target.as_slice(); + libfuzzer_test_one_input(buf); + ExitKind::Ok + }; + + // Create the executor for an in-process function with one observer for edge coverage and one for the execution time + let mut executor = TimeoutExecutor::new( + InProcessExecutor::new( + &mut harness, + tuple_list!(edges_observer, time_observer), + &mut fuzzer, + &mut state, + &mut restarting_mgr, + )?, + // 10 seconds timeout + Duration::new(10, 0), + ); + + // Initialize ASAN, call this before any ASAN crashes can occur (directly after initializing executor e.g.) + #[cfg(windows)] + unsafe { + libafl_targets::setup_asan_callback(&executor, &restarting_mgr, &fuzzer); + } + + // The actual target run starts here. + // Call LLVMFUzzerInitialize() if present. + let args: Vec = env::args().collect(); + if libfuzzer_initialize(&args) == -1 { + println!("Warning: LLVMFuzzerInitialize failed with -1") + } + + // In case the corpus is empty (on first run), reset + if state.corpus().count() < 1 { + state + .load_initial_inputs(&mut fuzzer, &mut executor, &mut restarting_mgr, corpus_dirs) + .unwrap_or_else(|_| panic!("Failed to load initial corpus at {:?}", &corpus_dirs)); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + + // This fuzzer restarts after 1 mio `fuzz_one` executions. + // Each fuzz_one will internally do many executions of the target. + // If your target is very instable, setting a low count here may help. + // However, you will lose a lot of performance that way. + let iters = 1_000_000; + fuzzer.fuzz_loop_for( + &mut stages, + &mut executor, + &mut state, + &mut restarting_mgr, + iters, + )?; + + // It's important, that we store the state before restarting! + // Else, the parent will not respawn a new child and quit. + restarting_mgr.on_restart(&mut state)?; + + Ok(()) +} diff --git a/libafl_cc/src/common-llvm.h b/libafl_cc/src/common-llvm.h index 26134e1c58..d1dacf847a 100644 --- a/libafl_cc/src/common-llvm.h +++ b/libafl_cc/src/common-llvm.h @@ -10,7 +10,7 @@ typedef long double max_align_t; #endif #if LLVM_VERSION_MAJOR >= 7 /* use new pass manager */ -//#define USE_NEW_PM 1 +// #define USE_NEW_PM 1 #endif /* #if LLVM_VERSION_STRING >= "4.0.1" */ diff --git a/libafl_targets/src/libfuzzer.c b/libafl_targets/src/libfuzzer.c index 6af25f03f2..4de0be79b1 100644 --- a/libafl_targets/src/libfuzzer.c +++ b/libafl_targets/src/libfuzzer.c @@ -22,6 +22,15 @@ EXT_FUNC_IMPL(main, int, (int argc, char** argv), false) { libafl_main(); return 0; } + +#if defined(_WIN32) +// If we do not add the main, the MSVC linker fails with: +// LINK : fatal error LNK1561: entry point must be defined +int main(int argc, char** argv) { + libafl_main(); +} +#endif + #pragma GCC diagnostic pop EXPORT_FN int libafl_targets_has_libfuzzer_init() {