[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 <tokazerkje@outlook.com>
Co-authored-by: Andrea Fioraldi <andreafioraldi@gmail.com>
Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
Max Ammann 2022-12-21 10:41:58 +01:00 committed by GitHub
parent 3c7dcac41d
commit 4d8b566a87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 436 additions and 1 deletions

View File

@ -0,0 +1 @@
libfuzzer_windows_asan*

View File

@ -0,0 +1,26 @@
[package]
name = "libfuzzer_windows_asan"
version = "0.8.2"
authors = ["Max Ammann <max@maxammann.org>"]
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"]

View File

@ -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
'''

View File

@ -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.

View File

@ -0,0 +1 @@
Hello World

View File

@ -0,0 +1,16 @@
#include <stdint.h>
#include <stdlib.h>
#include <string>
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;
}

View File

@ -0,0 +1,43 @@
use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper};
pub fn main() {
let args: Vec<String> = 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");
}
}

View File

@ -0,0 +1,5 @@
pub mod libafl_cc;
fn main() {
libafl_cc::main()
}

View File

@ -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<String> = 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(())
}

View File

@ -10,7 +10,7 @@ typedef long double max_align_t;
#endif #endif
#if LLVM_VERSION_MAJOR >= 7 /* use new pass manager */ #if LLVM_VERSION_MAJOR >= 7 /* use new pass manager */
//#define USE_NEW_PM 1 // #define USE_NEW_PM 1
#endif #endif
/* #if LLVM_VERSION_STRING >= "4.0.1" */ /* #if LLVM_VERSION_STRING >= "4.0.1" */

View File

@ -22,6 +22,15 @@ EXT_FUNC_IMPL(main, int, (int argc, char** argv), false) {
libafl_main(); libafl_main();
return 0; 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 #pragma GCC diagnostic pop
EXPORT_FN int libafl_targets_has_libfuzzer_init() { EXPORT_FN int libafl_targets_has_libfuzzer_init() {