libafl_qemu (#211)
* empty libafl_qemu crate * fuzzbench qemu fuzzer skeleton * emu.run() works without bp * working emu loop * resolve elf symbols * running Qemu fuzzer without coverage * qemu fuzzer with edge coverage * merge into inprocess::GLOBAL_STATE * create QemuExecutor and remove QemuEmulator * qemu hooks and persist edges mapping storing them in State * windows fix * add libafl_qemu to workspace * windows fix * some clippy * clippy * fix fuzzbench_qemu * fix fuzzbench_qemu makefile * fuck you macos
This commit is contained in:
parent
d7dad357e2
commit
d472a1242a
@ -11,6 +11,7 @@ members = [
|
|||||||
"libafl_cc",
|
"libafl_cc",
|
||||||
"libafl_targets",
|
"libafl_targets",
|
||||||
"libafl_frida",
|
"libafl_frida",
|
||||||
|
"libafl_qemu",
|
||||||
"libafl_tests",
|
"libafl_tests",
|
||||||
]
|
]
|
||||||
default-members = [
|
default-members = [
|
||||||
|
@ -34,7 +34,7 @@ use libafl::{
|
|||||||
token_mutations::I2SRandReplace,
|
token_mutations::I2SRandReplace,
|
||||||
tokens_mutations, Tokens,
|
tokens_mutations, Tokens,
|
||||||
},
|
},
|
||||||
observers::{StdMapObserver, TimeObserver},
|
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
|
||||||
stages::{StdMutationalStage, TracingStage},
|
stages::{StdMutationalStage, TracingStage},
|
||||||
state::{HasCorpus, HasMetadata, StdState},
|
state::{HasCorpus, HasMetadata, StdState},
|
||||||
stats::SimpleStats,
|
stats::SimpleStats,
|
||||||
|
2
fuzzers/fuzzbench_qemu/.gitignore
vendored
Normal file
2
fuzzers/fuzzbench_qemu/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
build/
|
||||||
|
qemu-libafl-bridge/
|
30
fuzzers/fuzzbench_qemu/Cargo.toml
Normal file
30
fuzzers/fuzzbench_qemu/Cargo.toml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
[package]
|
||||||
|
name = "fuzzbench_qemu"
|
||||||
|
version = "0.5.0"
|
||||||
|
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["std"]
|
||||||
|
std = []
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
opt-level = 3
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cc = { version = "1.0", features = ["parallel"] }
|
||||||
|
which = { version = "4.0.2" }
|
||||||
|
num_cpus = "1.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libafl = { path = "../../libafl/" }
|
||||||
|
libafl_qemu = { path = "../../libafl_qemu/" }
|
||||||
|
clap = { version = "3.0.0-beta.2", features = ["default"] }
|
||||||
|
nix = "0.20.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "fuzzbench_qemu"
|
||||||
|
crate-type = ["staticlib"]
|
61
fuzzers/fuzzbench_qemu/Makefile
Normal file
61
fuzzers/fuzzbench_qemu/Makefile
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
FUZZER_NAME=fuzzbench_qemu
|
||||||
|
PROJECT_DIR=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||||
|
|
||||||
|
ifeq ($(strip $(CPU_TARGET)),)
|
||||||
|
CPU_TARGET=$(shell uname -m)
|
||||||
|
ifeq ($(strip $(CPU_TARGET)),i686)
|
||||||
|
CPU_TARGET=i386
|
||||||
|
else ifeq ($(strip $(CPU_TARGET)),arm64v8)
|
||||||
|
CPU_TARGET=aarch64
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(strip $(BUILD_TARGET)),)
|
||||||
|
BUILD_TARGET=release
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(strip $(DEBUG)),1)
|
||||||
|
BUILD_TARGET=debug
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(strip $(BUILD_TARGET)),release)
|
||||||
|
CARGO_ARGS+= --release
|
||||||
|
endif
|
||||||
|
|
||||||
|
UNAME := $(shell uname)
|
||||||
|
|
||||||
|
.PHONY: clean short_test all
|
||||||
|
|
||||||
|
all: build/qemu-$(CPU_TARGET)
|
||||||
|
|
||||||
|
target/$(BUILD_TARGET)/lib$(FUZZER_NAME).a: src/*
|
||||||
|
cargo build $(CARGO_ARGS)
|
||||||
|
|
||||||
|
qemu-libafl-bridge:
|
||||||
|
git clone git@github.com:AFLplusplus/qemu-libafl-bridge.git
|
||||||
|
|
||||||
|
build/config.status: qemu-libafl-bridge qemu-libafl-bridge/configure
|
||||||
|
mkdir -p build
|
||||||
|
cd build && ../qemu-libafl-bridge/configure --target-list=$(CPU_TARGET)-linux-user --with-libafl-bridge="$(PROJECT_DIR)/target/$(BUILD_TARGET)/lib$(FUZZER_NAME).a"
|
||||||
|
|
||||||
|
build/qemu-$(CPU_TARGET): target/$(BUILD_TARGET)/lib$(FUZZER_NAME).a build/config.status
|
||||||
|
$(MAKE) -C build
|
||||||
|
|
||||||
|
pull: qemu-libafl-bridge
|
||||||
|
cd qemu-libafl-bridge && git pull
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf build
|
||||||
|
cargo clean
|
||||||
|
|
||||||
|
ifeq ($(UNAME), Linux)
|
||||||
|
|
||||||
|
short_test: target/$(BUILD_TARGET)/lib$(FUZZER_NAME).a
|
||||||
|
@echo "Skipping short test"
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
short_test:
|
||||||
|
@echo "Skipping build and short test"
|
||||||
|
|
||||||
|
endif
|
17
fuzzers/fuzzbench_qemu/README.md
Normal file
17
fuzzers/fuzzbench_qemu/README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Fuzzbench Harness
|
||||||
|
|
||||||
|
This folder contains an example fuzzer tailored for fuzzbench.
|
||||||
|
It uses the best possible setting, with the exception of a SimpleRestartingEventManager instead of an LlmpEventManager - since fuzzbench is single threaded.
|
||||||
|
Real fuzz campaigns should consider using multithreaded LlmpEventManager, see the other examples.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
To build this example, run `cargo build --release`.
|
||||||
|
This will build the fuzzer compilers (`libafl_cc` and `libafl_cpp`) with `src/lib.rs` as fuzzer.
|
||||||
|
The fuzzer uses the libfuzzer compatibility layer and the SanitizerCoverage runtime functions for coverage feedback.
|
||||||
|
|
||||||
|
These can then be used to build libfuzzer harnesses in the software project of your choice.
|
||||||
|
Finally, just run the resulting binary with `out_dir`, `in_dir`.
|
||||||
|
|
||||||
|
In any real-world scenario, you should use `taskset` to pin each client to an empty CPU core, the lib does not pick an empty core automatically (yet).
|
||||||
|
|
16
fuzzers/fuzzbench_qemu/fuzz.c
Normal file
16
fuzzers/fuzzbench_qemu/fuzz.c
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||||
|
// printf("Got %ld bytes.\n", Size);
|
||||||
|
if (Size >= 4 && *(uint16_t*)Data == 0xaabb && *(uint16_t*)&Data[2] == 0xccab)
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
|
||||||
|
char buf [10] = {0};
|
||||||
|
LLVMFuzzerTestOneInput(buf, 10);
|
||||||
|
|
||||||
|
}
|
342
fuzzers/fuzzbench_qemu/src/fuzzer.rs
Normal file
342
fuzzers/fuzzbench_qemu/src/fuzzer.rs
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
//! A singlethreaded QEMU fuzzer that can auto-restart.
|
||||||
|
|
||||||
|
use clap::{App, Arg};
|
||||||
|
use core::{cell::RefCell, time::Duration};
|
||||||
|
#[cfg(unix)]
|
||||||
|
use nix::{self, unistd::dup};
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||||
|
use std::{
|
||||||
|
env,
|
||||||
|
fs::{self, File, OpenOptions},
|
||||||
|
io::{self, Write},
|
||||||
|
path::PathBuf,
|
||||||
|
process,
|
||||||
|
};
|
||||||
|
|
||||||
|
use libafl::{
|
||||||
|
bolts::{
|
||||||
|
current_nanos, current_time,
|
||||||
|
os::dup2,
|
||||||
|
rands::StdRand,
|
||||||
|
shmem::{ShMemProvider, StdShMemProvider},
|
||||||
|
tuples::{tuple_list, Merge},
|
||||||
|
},
|
||||||
|
corpus::{Corpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, QueueCorpusScheduler},
|
||||||
|
events::SimpleRestartingEventManager,
|
||||||
|
executors::{ExitKind, TimeoutExecutor},
|
||||||
|
feedback_or, feedback_or_fast,
|
||||||
|
feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
|
||||||
|
fuzzer::{Fuzzer, StdFuzzer},
|
||||||
|
inputs::{BytesInput, HasTargetBytes},
|
||||||
|
mutators::{
|
||||||
|
scheduled::{havoc_mutations, StdScheduledMutator},
|
||||||
|
tokens_mutations, Tokens,
|
||||||
|
},
|
||||||
|
observers::{HitcountsMapObserver, TimeObserver, VariableMapObserver},
|
||||||
|
stages::StdMutationalStage,
|
||||||
|
state::{HasCorpus, HasMetadata, StdState},
|
||||||
|
stats::SimpleStats,
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
use libafl_qemu::{
|
||||||
|
amd64::Amd64Regs, elf::EasyElf, emu, filter_qemu_args, hooks, MmapPerms, QemuExecutor,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The fuzzer main (as `no_mangle` C function)
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn libafl_qemu_main() {
|
||||||
|
// Registry the metadata types used in this fuzzer
|
||||||
|
// Needed only on no_std
|
||||||
|
//RegistryBuilder::register::<Tokens>();
|
||||||
|
|
||||||
|
let res = match App::new("libafl_qemu_fuzzbench")
|
||||||
|
.version("0.4.0")
|
||||||
|
.author("AFLplusplus team")
|
||||||
|
.about("LibAFL-based fuzzer with QEMU for Fuzzbench")
|
||||||
|
.arg(
|
||||||
|
Arg::new("out")
|
||||||
|
.about("The directory to place finds in ('corpus')")
|
||||||
|
.long("libafl-out")
|
||||||
|
.required(true)
|
||||||
|
.takes_value(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("in")
|
||||||
|
.about("The directory to read initial inputs from ('seeds')")
|
||||||
|
.long("libafl-in")
|
||||||
|
.required(true)
|
||||||
|
.takes_value(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("tokens")
|
||||||
|
.long("libafl-tokens")
|
||||||
|
.about("A file to read tokens from, to be used during fuzzing")
|
||||||
|
.takes_value(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("logfile")
|
||||||
|
.long("libafl-logfile")
|
||||||
|
.about("Duplicates all output to this file")
|
||||||
|
.default_value("libafl.log"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("timeout")
|
||||||
|
.long("libafl-timeout")
|
||||||
|
.about("Timeout for each individual execution, in milliseconds")
|
||||||
|
.default_value("1000"),
|
||||||
|
)
|
||||||
|
.try_get_matches_from(filter_qemu_args())
|
||||||
|
{
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(err) => {
|
||||||
|
println!(
|
||||||
|
"Syntax: {}, --libafl-in <input> --libafl-out <output>\n{:?}",
|
||||||
|
env::current_exe()
|
||||||
|
.unwrap_or_else(|_| "fuzzer".into())
|
||||||
|
.to_string_lossy(),
|
||||||
|
err.info,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Workdir: {:?}",
|
||||||
|
env::current_dir().unwrap().to_string_lossy().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
// For fuzzbench, crashes and finds are inside the same `corpus` directory, in the "queue" and "crashes" subdir.
|
||||||
|
let mut out_dir = PathBuf::from(res.value_of("out").unwrap().to_string());
|
||||||
|
if fs::create_dir(&out_dir).is_err() {
|
||||||
|
println!("Out dir at {:?} already exists.", &out_dir);
|
||||||
|
if !out_dir.is_dir() {
|
||||||
|
println!("Out dir at {:?} is not a valid directory!", &out_dir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut crashes = out_dir.clone();
|
||||||
|
crashes.push("crashes");
|
||||||
|
out_dir.push("queue");
|
||||||
|
|
||||||
|
let in_dir = PathBuf::from(res.value_of("in").unwrap().to_string());
|
||||||
|
if !in_dir.is_dir() {
|
||||||
|
println!("In dir at {:?} is not a valid directory!", &in_dir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tokens = res.value_of("tokens").map(PathBuf::from);
|
||||||
|
|
||||||
|
let logfile = PathBuf::from(res.value_of("logfile").unwrap().to_string());
|
||||||
|
|
||||||
|
let timeout = Duration::from_millis(
|
||||||
|
res.value_of("timeout")
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
.parse()
|
||||||
|
.expect("Could not parse timeout in milliseconds"),
|
||||||
|
);
|
||||||
|
|
||||||
|
fuzz(out_dir, crashes, in_dir, tokens, logfile, timeout)
|
||||||
|
.expect("An error occurred while fuzzing");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The actual fuzzer
|
||||||
|
fn fuzz(
|
||||||
|
corpus_dir: PathBuf,
|
||||||
|
objective_dir: PathBuf,
|
||||||
|
seed_dir: PathBuf,
|
||||||
|
tokenfile: Option<PathBuf>,
|
||||||
|
logfile: PathBuf,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut elf_buffer = Vec::new();
|
||||||
|
let elf = EasyElf::from_file(emu::binary_path(), &mut elf_buffer)?;
|
||||||
|
|
||||||
|
let test_one_input_ptr = elf.resolve_symbol("LLVMFuzzerTestOneInput").unwrap();
|
||||||
|
println!("LLVMFuzzerTestOneInput @ {:#x}", test_one_input_ptr);
|
||||||
|
|
||||||
|
emu::set_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput
|
||||||
|
emu::run();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Break at {:#x}",
|
||||||
|
emu::read_reg::<_, u64>(Amd64Regs::Rip).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let stack_ptr: u64 = emu::read_reg(Amd64Regs::Rsp).unwrap();
|
||||||
|
let mut ret_addr = [0u64];
|
||||||
|
emu::read_mem(stack_ptr, &mut ret_addr);
|
||||||
|
let ret_addr = ret_addr[0];
|
||||||
|
|
||||||
|
println!("Stack pointer = {:#x}", stack_ptr);
|
||||||
|
println!("Return address = {:#x}", ret_addr);
|
||||||
|
|
||||||
|
emu::remove_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput
|
||||||
|
emu::set_breakpoint(ret_addr); // LLVMFuzzerTestOneInput ret addr
|
||||||
|
|
||||||
|
let input_addr = emu::map_private(0, 4096, MmapPerms::ReadWrite).unwrap();
|
||||||
|
println!("Placing input at {:#x}", input_addr);
|
||||||
|
|
||||||
|
let log = RefCell::new(
|
||||||
|
OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.create(true)
|
||||||
|
.open(&logfile)?,
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
let mut stdout_cpy = unsafe {
|
||||||
|
let new_fd = dup(io::stdout().as_raw_fd())?;
|
||||||
|
File::from_raw_fd(new_fd)
|
||||||
|
};
|
||||||
|
#[cfg(unix)]
|
||||||
|
let file_null = File::open("/dev/null")?;
|
||||||
|
|
||||||
|
// 'While the stats are state, they are usually used in the broker - which is likely never restarted
|
||||||
|
let stats = SimpleStats::new(|s| {
|
||||||
|
#[cfg(unix)]
|
||||||
|
writeln!(&mut stdout_cpy, "{}", s).unwrap();
|
||||||
|
#[cfg(windows)]
|
||||||
|
println!("{}", s);
|
||||||
|
writeln!(log.borrow_mut(), "{:?} {}", current_time(), s).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut shmem_provider = StdShMemProvider::new()?;
|
||||||
|
|
||||||
|
let (state, mut mgr) = match SimpleRestartingEventManager::launch(stats, &mut shmem_provider) {
|
||||||
|
// The restarting state will spawn the same process again as child, then restarted it each time it crashes.
|
||||||
|
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 hooks::EDGES_MAP };
|
||||||
|
let edges_counter = unsafe { &mut hooks::MAX_EDGES_NUM };
|
||||||
|
let edges_observer =
|
||||||
|
HitcountsMapObserver::new(VariableMapObserver::new("edges", edges, edges_counter));
|
||||||
|
|
||||||
|
// Create an observation channel to keep track of the execution time
|
||||||
|
let time_observer = TimeObserver::new("time");
|
||||||
|
|
||||||
|
// The state of the edges feedback.
|
||||||
|
let feedback_state = MapFeedbackState::with_observer(&edges_observer);
|
||||||
|
|
||||||
|
// Feedback to rate the interestingness of an input
|
||||||
|
// This one is composed by two Feedbacks in OR
|
||||||
|
let feedback = feedback_or!(
|
||||||
|
// New maximization map feedback linked to the edges observer and the feedback state
|
||||||
|
MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false),
|
||||||
|
// 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 objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
|
||||||
|
|
||||||
|
// 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
|
||||||
|
OnDiskCorpus::new(corpus_dir).unwrap(),
|
||||||
|
// 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.
|
||||||
|
// They are the data related to the feedbacks that you want to persist in the State.
|
||||||
|
tuple_list!(feedback_state),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
// A minimization+queue policy to get testcasess from the corpus
|
||||||
|
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
|
||||||
|
|
||||||
|
// 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 mut buf = target.as_slice();
|
||||||
|
if buf.len() > 32 {
|
||||||
|
buf = &buf[0..32];
|
||||||
|
}
|
||||||
|
|
||||||
|
emu::write_mem(input_addr, buf);
|
||||||
|
|
||||||
|
emu::write_reg(Amd64Regs::Rdi, input_addr).unwrap();
|
||||||
|
emu::write_reg(Amd64Regs::Rsi, buf.len()).unwrap();
|
||||||
|
emu::write_reg(Amd64Regs::Rip, test_one_input_ptr).unwrap();
|
||||||
|
emu::write_reg(Amd64Regs::Rsp, stack_ptr).unwrap();
|
||||||
|
|
||||||
|
emu::run();
|
||||||
|
|
||||||
|
ExitKind::Ok
|
||||||
|
};
|
||||||
|
|
||||||
|
let executor = QemuExecutor::new(
|
||||||
|
&mut harness,
|
||||||
|
tuple_list!(edges_observer, time_observer),
|
||||||
|
&mut fuzzer,
|
||||||
|
&mut state,
|
||||||
|
&mut mgr,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
executor.hook_edge_generation(hooks::gen_unique_edges_id);
|
||||||
|
executor.hook_edge_execution(hooks::exec_log_hitcount);
|
||||||
|
|
||||||
|
// 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(executor, timeout);
|
||||||
|
|
||||||
|
// Read tokens
|
||||||
|
if let Some(tokenfile) = tokenfile {
|
||||||
|
if state.metadata().get::<Tokens>().is_none() {
|
||||||
|
state.add_metadata(Tokens::from_tokens_file(tokenfile)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.corpus().count() < 1 {
|
||||||
|
state
|
||||||
|
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &[seed_dir.clone()])
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
println!("Failed to load initial corpus at {:?}", &seed_dir);
|
||||||
|
process::exit(0);
|
||||||
|
});
|
||||||
|
println!("We imported {} inputs from disk.", state.corpus().count());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup a mutational stage with a basic bytes mutator
|
||||||
|
let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
|
||||||
|
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
|
||||||
|
|
||||||
|
// Remove target ouput (logs still survive)
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
let null_fd = file_null.as_raw_fd();
|
||||||
|
dup2(null_fd, io::stdout().as_raw_fd())?;
|
||||||
|
dup2(null_fd, io::stderr().as_raw_fd())?;
|
||||||
|
}
|
||||||
|
// reopen file to make sure we're at the end
|
||||||
|
log.replace(
|
||||||
|
OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.create(true)
|
||||||
|
.open(&logfile)?,
|
||||||
|
);
|
||||||
|
|
||||||
|
fuzzer
|
||||||
|
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
|
||||||
|
.expect("Error in the fuzzing loop");
|
||||||
|
|
||||||
|
// Never reached
|
||||||
|
Ok(())
|
||||||
|
}
|
2
fuzzers/fuzzbench_qemu/src/lib.rs
Normal file
2
fuzzers/fuzzbench_qemu/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub mod fuzzer;
|
@ -15,11 +15,6 @@ use crate::bolts::os::unix_signals::setup_signal_handler;
|
|||||||
#[cfg(all(windows, feature = "std"))]
|
#[cfg(all(windows, feature = "std"))]
|
||||||
use crate::bolts::os::windows_exceptions::setup_exception_handler;
|
use crate::bolts::os::windows_exceptions::setup_exception_handler;
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
pub use unix_signal_handler::{nop_handler, HandlerFuncPtr};
|
|
||||||
#[cfg(all(windows, feature = "std"))]
|
|
||||||
pub use windows_exception_handler::{nop_handler, HandlerFuncPtr};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
corpus::Corpus,
|
corpus::Corpus,
|
||||||
events::{EventFirer, EventRestarter},
|
events::{EventFirer, EventRestarter},
|
||||||
@ -44,11 +39,9 @@ where
|
|||||||
/// The observers, observing each run
|
/// The observers, observing each run
|
||||||
observers: OT,
|
observers: OT,
|
||||||
/// On crash C function pointer
|
/// On crash C function pointer
|
||||||
#[cfg(any(unix, all(windows, feature = "std")))]
|
crash_handler: *const c_void,
|
||||||
crash_handler: HandlerFuncPtr,
|
|
||||||
/// On timeout C function pointer
|
/// On timeout C function pointer
|
||||||
#[cfg(unix)]
|
timeout_handler: *const c_void,
|
||||||
timeout_handler: HandlerFuncPtr,
|
|
||||||
phantom: PhantomData<(I, S)>,
|
phantom: PhantomData<(I, S)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +61,7 @@ where
|
|||||||
) -> Result<ExitKind, Error> {
|
) -> Result<ExitKind, Error> {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
unsafe {
|
unsafe {
|
||||||
let data = &mut unix_signal_handler::GLOBAL_STATE;
|
let data = &mut GLOBAL_STATE;
|
||||||
write_volatile(
|
write_volatile(
|
||||||
&mut data.current_input_ptr,
|
&mut data.current_input_ptr,
|
||||||
input as *const _ as *const c_void,
|
input as *const _ as *const c_void,
|
||||||
@ -88,7 +81,7 @@ where
|
|||||||
}
|
}
|
||||||
#[cfg(all(windows, feature = "std"))]
|
#[cfg(all(windows, feature = "std"))]
|
||||||
unsafe {
|
unsafe {
|
||||||
let data = &mut windows_exception_handler::GLOBAL_STATE;
|
let data = &mut GLOBAL_STATE;
|
||||||
write_volatile(
|
write_volatile(
|
||||||
&mut data.current_input_ptr,
|
&mut data.current_input_ptr,
|
||||||
input as *const _ as *const c_void,
|
input as *const _ as *const c_void,
|
||||||
@ -111,18 +104,12 @@ where
|
|||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
unsafe {
|
unsafe {
|
||||||
write_volatile(
|
write_volatile(&mut GLOBAL_STATE.current_input_ptr, ptr::null());
|
||||||
&mut unix_signal_handler::GLOBAL_STATE.current_input_ptr,
|
|
||||||
ptr::null(),
|
|
||||||
);
|
|
||||||
compiler_fence(Ordering::SeqCst);
|
compiler_fence(Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
#[cfg(all(windows, feature = "std"))]
|
#[cfg(all(windows, feature = "std"))]
|
||||||
unsafe {
|
unsafe {
|
||||||
write_volatile(
|
write_volatile(&mut GLOBAL_STATE.current_input_ptr, ptr::null());
|
||||||
&mut windows_exception_handler::GLOBAL_STATE.current_input_ptr,
|
|
||||||
ptr::null(),
|
|
||||||
);
|
|
||||||
compiler_fence(Ordering::SeqCst);
|
compiler_fence(Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,14 +162,15 @@ where
|
|||||||
{
|
{
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
unsafe {
|
unsafe {
|
||||||
let data = &mut unix_signal_handler::GLOBAL_STATE;
|
let data = &mut GLOBAL_STATE;
|
||||||
setup_signal_handler(data)?;
|
setup_signal_handler(data)?;
|
||||||
compiler_fence(Ordering::SeqCst);
|
compiler_fence(Ordering::SeqCst);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
harness_fn,
|
harness_fn,
|
||||||
observers,
|
observers,
|
||||||
crash_handler: unix_signal_handler::inproc_crash_handler::<EM, I, OC, OF, OT, S, Z>,
|
crash_handler: unix_signal_handler::inproc_crash_handler::<EM, I, OC, OF, OT, S, Z>
|
||||||
|
as *const _,
|
||||||
timeout_handler: unix_signal_handler::inproc_timeout_handler::<
|
timeout_handler: unix_signal_handler::inproc_timeout_handler::<
|
||||||
EM,
|
EM,
|
||||||
I,
|
I,
|
||||||
@ -191,13 +179,13 @@ where
|
|||||||
OT,
|
OT,
|
||||||
S,
|
S,
|
||||||
Z,
|
Z,
|
||||||
>,
|
> as *const _,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
#[cfg(all(windows, feature = "std"))]
|
#[cfg(all(windows, feature = "std"))]
|
||||||
unsafe {
|
unsafe {
|
||||||
let data = &mut windows_exception_handler::GLOBAL_STATE;
|
let data = &mut GLOBAL_STATE;
|
||||||
setup_exception_handler(data)?;
|
setup_exception_handler(data)?;
|
||||||
compiler_fence(Ordering::SeqCst);
|
compiler_fence(Ordering::SeqCst);
|
||||||
|
|
||||||
@ -212,8 +200,9 @@ where
|
|||||||
OT,
|
OT,
|
||||||
S,
|
S,
|
||||||
Z,
|
Z,
|
||||||
>,
|
> as *const _,
|
||||||
// timeout_handler: windows_exception_handler::inproc_timeout_handler::<EM, I, OC, OF, OT, S, Z>,
|
// timeout_handler: windows_exception_handler::inproc_timeout_handler::<EM, I, OC, OF, OT, S, Z> as *const _,
|
||||||
|
timeout_handler: ptr::null(),
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -238,11 +227,42 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct InProcessExecutorHandlerData {
|
||||||
|
pub state_ptr: *mut c_void,
|
||||||
|
pub event_mgr_ptr: *mut c_void,
|
||||||
|
pub fuzzer_ptr: *mut c_void,
|
||||||
|
pub observers_ptr: *const c_void,
|
||||||
|
pub current_input_ptr: *const c_void,
|
||||||
|
pub crash_handler: *const c_void,
|
||||||
|
pub timeout_handler: *const c_void,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for InProcessExecutorHandlerData {}
|
||||||
|
unsafe impl Sync for InProcessExecutorHandlerData {}
|
||||||
|
|
||||||
|
/// Exception handling needs some nasty unsafe.
|
||||||
|
pub static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExecutorHandlerData {
|
||||||
|
/// The state ptr for signal handling
|
||||||
|
state_ptr: ptr::null_mut(),
|
||||||
|
/// The event manager ptr for signal handling
|
||||||
|
event_mgr_ptr: ptr::null_mut(),
|
||||||
|
/// The fuzzer ptr for signal handling
|
||||||
|
fuzzer_ptr: ptr::null_mut(),
|
||||||
|
/// The observers ptr for signal handling
|
||||||
|
observers_ptr: ptr::null(),
|
||||||
|
/// The current input for signal handling
|
||||||
|
current_input_ptr: ptr::null(),
|
||||||
|
/// The crash handler fn
|
||||||
|
crash_handler: ptr::null(),
|
||||||
|
/// The timeout handler fn
|
||||||
|
timeout_handler: ptr::null(),
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
mod unix_signal_handler {
|
mod unix_signal_handler {
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::ptr;
|
use core::{mem::transmute, ptr};
|
||||||
use libc::{c_void, siginfo_t, ucontext_t};
|
use libc::{siginfo_t, ucontext_t};
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::io::{stdout, Write};
|
use std::io::{stdout, Write};
|
||||||
|
|
||||||
@ -250,7 +270,10 @@ mod unix_signal_handler {
|
|||||||
bolts::os::unix_signals::{Handler, Signal},
|
bolts::os::unix_signals::{Handler, Signal},
|
||||||
corpus::{Corpus, Testcase},
|
corpus::{Corpus, Testcase},
|
||||||
events::{Event, EventFirer, EventRestarter},
|
events::{Event, EventFirer, EventRestarter},
|
||||||
executors::ExitKind,
|
executors::{
|
||||||
|
inprocess::{InProcessExecutorHandlerData, GLOBAL_STATE},
|
||||||
|
ExitKind,
|
||||||
|
},
|
||||||
feedbacks::Feedback,
|
feedbacks::Feedback,
|
||||||
fuzzer::HasObjective,
|
fuzzer::HasObjective,
|
||||||
inputs::Input,
|
inputs::Input,
|
||||||
@ -261,47 +284,14 @@ mod unix_signal_handler {
|
|||||||
pub type HandlerFuncPtr =
|
pub type HandlerFuncPtr =
|
||||||
unsafe fn(Signal, siginfo_t, &mut ucontext_t, data: &mut InProcessExecutorHandlerData);
|
unsafe fn(Signal, siginfo_t, &mut ucontext_t, data: &mut InProcessExecutorHandlerData);
|
||||||
|
|
||||||
// TODO merge GLOBAL_STATE with the Windows one
|
|
||||||
|
|
||||||
/// Signal handling on unix systems needs some nasty unsafe.
|
|
||||||
pub static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExecutorHandlerData {
|
|
||||||
/// The state ptr for signal handling
|
|
||||||
state_ptr: ptr::null_mut(),
|
|
||||||
/// The event manager ptr for signal handling
|
|
||||||
event_mgr_ptr: ptr::null_mut(),
|
|
||||||
/// The fuzzer ptr for signal handling
|
|
||||||
fuzzer_ptr: ptr::null_mut(),
|
|
||||||
/// The observers ptr for signal handling
|
|
||||||
observers_ptr: ptr::null(),
|
|
||||||
/// The current input for signal handling
|
|
||||||
current_input_ptr: ptr::null(),
|
|
||||||
/// The crash handler fn
|
|
||||||
crash_handler: nop_handler,
|
|
||||||
/// The timeout handler fn
|
|
||||||
timeout_handler: nop_handler,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct InProcessExecutorHandlerData {
|
|
||||||
pub state_ptr: *mut c_void,
|
|
||||||
pub event_mgr_ptr: *mut c_void,
|
|
||||||
pub fuzzer_ptr: *mut c_void,
|
|
||||||
pub observers_ptr: *const c_void,
|
|
||||||
pub current_input_ptr: *const c_void,
|
|
||||||
pub crash_handler: HandlerFuncPtr,
|
|
||||||
pub timeout_handler: HandlerFuncPtr,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for InProcessExecutorHandlerData {}
|
|
||||||
unsafe impl Sync for InProcessExecutorHandlerData {}
|
|
||||||
|
|
||||||
/// A handler that does nothing.
|
/// A handler that does nothing.
|
||||||
pub fn nop_handler(
|
/*pub fn nop_handler(
|
||||||
_signal: Signal,
|
_signal: Signal,
|
||||||
_info: siginfo_t,
|
_info: siginfo_t,
|
||||||
_context: &mut ucontext_t,
|
_context: &mut ucontext_t,
|
||||||
_data: &mut InProcessExecutorHandlerData,
|
_data: &mut InProcessExecutorHandlerData,
|
||||||
) {
|
) {
|
||||||
}
|
}*/
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
impl Handler for InProcessExecutorHandlerData {
|
impl Handler for InProcessExecutorHandlerData {
|
||||||
@ -310,9 +300,17 @@ mod unix_signal_handler {
|
|||||||
let data = &mut GLOBAL_STATE;
|
let data = &mut GLOBAL_STATE;
|
||||||
match signal {
|
match signal {
|
||||||
Signal::SigUser2 | Signal::SigAlarm => {
|
Signal::SigUser2 | Signal::SigAlarm => {
|
||||||
(data.timeout_handler)(signal, info, context, data);
|
if !data.timeout_handler.is_null() {
|
||||||
|
let func: HandlerFuncPtr = transmute(data.timeout_handler);
|
||||||
|
(func)(signal, info, context, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if !data.crash_handler.is_null() {
|
||||||
|
let func: HandlerFuncPtr = transmute(data.crash_handler);
|
||||||
|
(func)(signal, info, context, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => (data.crash_handler)(signal, info, context, data),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -544,7 +542,7 @@ mod unix_signal_handler {
|
|||||||
#[cfg(all(windows, feature = "std"))]
|
#[cfg(all(windows, feature = "std"))]
|
||||||
mod windows_exception_handler {
|
mod windows_exception_handler {
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::{ffi::c_void, ptr};
|
use core::{mem::transmute, ptr};
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::io::{stdout, Write};
|
use std::io::{stdout, Write};
|
||||||
|
|
||||||
@ -557,7 +555,10 @@ mod windows_exception_handler {
|
|||||||
},
|
},
|
||||||
corpus::{Corpus, Testcase},
|
corpus::{Corpus, Testcase},
|
||||||
events::{Event, EventFirer, EventRestarter},
|
events::{Event, EventFirer, EventRestarter},
|
||||||
executors::ExitKind,
|
executors::{
|
||||||
|
inprocess::{InProcessExecutorHandlerData, GLOBAL_STATE},
|
||||||
|
ExitKind,
|
||||||
|
},
|
||||||
feedbacks::Feedback,
|
feedbacks::Feedback,
|
||||||
fuzzer::HasObjective,
|
fuzzer::HasObjective,
|
||||||
inputs::Input,
|
inputs::Input,
|
||||||
@ -568,49 +569,22 @@ mod windows_exception_handler {
|
|||||||
pub type HandlerFuncPtr =
|
pub type HandlerFuncPtr =
|
||||||
unsafe fn(ExceptionCode, *mut EXCEPTION_POINTERS, &mut InProcessExecutorHandlerData);
|
unsafe fn(ExceptionCode, *mut EXCEPTION_POINTERS, &mut InProcessExecutorHandlerData);
|
||||||
|
|
||||||
/// Signal handling on unix systems needs some nasty unsafe.
|
/*pub unsafe fn nop_handler(
|
||||||
pub static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExecutorHandlerData {
|
|
||||||
/// The state ptr for signal handling
|
|
||||||
state_ptr: ptr::null_mut(),
|
|
||||||
/// The event manager ptr for signal handling
|
|
||||||
event_mgr_ptr: ptr::null_mut(),
|
|
||||||
/// The fuzzer ptr for signal handling
|
|
||||||
fuzzer_ptr: ptr::null_mut(),
|
|
||||||
/// The observers ptr for signal handling
|
|
||||||
observers_ptr: ptr::null(),
|
|
||||||
/// The current input for signal handling
|
|
||||||
current_input_ptr: ptr::null(),
|
|
||||||
/// The crash handler fn
|
|
||||||
crash_handler: nop_handler,
|
|
||||||
// The timeout handler fn
|
|
||||||
//timeout_handler: nop_handler,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct InProcessExecutorHandlerData {
|
|
||||||
pub state_ptr: *mut c_void,
|
|
||||||
pub event_mgr_ptr: *mut c_void,
|
|
||||||
pub fuzzer_ptr: *mut c_void,
|
|
||||||
pub observers_ptr: *const c_void,
|
|
||||||
pub current_input_ptr: *const c_void,
|
|
||||||
pub crash_handler: HandlerFuncPtr,
|
|
||||||
//pub timeout_handler: HandlerFuncPtr,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for InProcessExecutorHandlerData {}
|
|
||||||
unsafe impl Sync for InProcessExecutorHandlerData {}
|
|
||||||
|
|
||||||
pub unsafe fn nop_handler(
|
|
||||||
_code: ExceptionCode,
|
_code: ExceptionCode,
|
||||||
_exception_pointers: *mut EXCEPTION_POINTERS,
|
_exception_pointers: *mut EXCEPTION_POINTERS,
|
||||||
_data: &mut InProcessExecutorHandlerData,
|
_data: &mut InProcessExecutorHandlerData,
|
||||||
) {
|
) {
|
||||||
}
|
}*/
|
||||||
|
|
||||||
impl Handler for InProcessExecutorHandlerData {
|
impl Handler for InProcessExecutorHandlerData {
|
||||||
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||||
fn handle(&mut self, code: ExceptionCode, exception_pointers: *mut EXCEPTION_POINTERS) {
|
fn handle(&mut self, code: ExceptionCode, exception_pointers: *mut EXCEPTION_POINTERS) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let data = &mut GLOBAL_STATE;
|
let data = &mut GLOBAL_STATE;
|
||||||
(data.crash_handler)(code, exception_pointers, data)
|
if !data.crash_handler.is_null() {
|
||||||
|
let func: HandlerFuncPtr = transmute(data.crash_handler);
|
||||||
|
(func)(code, exception_pointers, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -718,11 +692,11 @@ mod windows_exception_handler {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use core::marker::PhantomData;
|
use core::{marker::PhantomData, ptr};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
bolts::tuples::tuple_list,
|
bolts::tuples::tuple_list,
|
||||||
executors::{inprocess, Executor, ExitKind, InProcessExecutor},
|
executors::{Executor, ExitKind, InProcessExecutor},
|
||||||
inputs::NopInput,
|
inputs::NopInput,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -733,10 +707,8 @@ mod tests {
|
|||||||
let mut in_process_executor = InProcessExecutor::<_, NopInput, (), ()> {
|
let mut in_process_executor = InProcessExecutor::<_, NopInput, (), ()> {
|
||||||
harness_fn: &mut harness,
|
harness_fn: &mut harness,
|
||||||
observers: tuple_list!(),
|
observers: tuple_list!(),
|
||||||
#[cfg(any(unix, all(windows, feature = "std")))]
|
crash_handler: ptr::null(),
|
||||||
crash_handler: inprocess::nop_handler,
|
timeout_handler: ptr::null(),
|
||||||
#[cfg(unix)]
|
|
||||||
timeout_handler: inprocess::nop_handler,
|
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
};
|
};
|
||||||
let input = NopInput {};
|
let input = NopInput {};
|
||||||
|
@ -14,7 +14,7 @@ pub mod combined;
|
|||||||
pub use combined::CombinedExecutor;
|
pub use combined::CombinedExecutor;
|
||||||
|
|
||||||
pub mod shadow;
|
pub mod shadow;
|
||||||
pub use shadow::{HasShadowObserverHooks, ShadowExecutor};
|
pub use shadow::ShadowExecutor;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
inputs::{HasTargetBytes, Input},
|
inputs::{HasTargetBytes, Input},
|
||||||
|
@ -9,26 +9,6 @@ use crate::{
|
|||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait HasShadowObserverHooks<EM, I, S, SOT, Z> {
|
|
||||||
/// Run the pre exec hook for all the shadow [`crate::observers::Observer`]`s`
|
|
||||||
fn pre_exec_shadow_observers(
|
|
||||||
&mut self,
|
|
||||||
fuzzer: &mut Z,
|
|
||||||
state: &mut S,
|
|
||||||
mgr: &mut EM,
|
|
||||||
input: &I,
|
|
||||||
) -> Result<(), Error>;
|
|
||||||
|
|
||||||
/// Run the post exec hook for all the shadow [`crate::observers::Observer`]`s`
|
|
||||||
fn post_exec_shadow_observers(
|
|
||||||
&mut self,
|
|
||||||
fuzzer: &mut Z,
|
|
||||||
state: &mut S,
|
|
||||||
mgr: &mut EM,
|
|
||||||
input: &I,
|
|
||||||
) -> Result<(), Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A [`ShadowExecutor`] wraps an executor and a set of shadow observers
|
/// A [`ShadowExecutor`] wraps an executor and a set of shadow observers
|
||||||
pub struct ShadowExecutor<E, I, S, SOT> {
|
pub struct ShadowExecutor<E, I, S, SOT> {
|
||||||
executor: E,
|
executor: E,
|
||||||
|
@ -467,6 +467,61 @@ where
|
|||||||
FT: FeedbackStatesTuple,
|
FT: FeedbackStatesTuple,
|
||||||
SC: Corpus<I>,
|
SC: Corpus<I>,
|
||||||
{
|
{
|
||||||
|
fn generate_initial_internal<G, E, EM, Z>(
|
||||||
|
&mut self,
|
||||||
|
fuzzer: &mut Z,
|
||||||
|
executor: &mut E,
|
||||||
|
generator: &mut G,
|
||||||
|
manager: &mut EM,
|
||||||
|
num: usize,
|
||||||
|
forced: bool,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
G: Generator<I, R>,
|
||||||
|
Z: Evaluator<E, EM, I, Self>,
|
||||||
|
EM: EventFirer<I, Self>,
|
||||||
|
{
|
||||||
|
let mut added = 0;
|
||||||
|
for _ in 0..num {
|
||||||
|
let input = generator.generate(self.rand_mut())?;
|
||||||
|
if forced {
|
||||||
|
let _ = fuzzer.add_input(self, executor, manager, input)?;
|
||||||
|
added += 1;
|
||||||
|
} else {
|
||||||
|
let (res, _) = fuzzer.evaluate_input(self, executor, manager, input)?;
|
||||||
|
if res != ExecuteInputResult::None {
|
||||||
|
added += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
manager.fire(
|
||||||
|
self,
|
||||||
|
Event::Log {
|
||||||
|
severity_level: LogSeverity::Debug,
|
||||||
|
message: format!("Loaded {} over {} initial testcases", added, num),
|
||||||
|
phantom: PhantomData,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate `num` initial inputs, using the passed-in generator and force the addition to corpus.
|
||||||
|
pub fn generate_initial_inputs_forced<G, E, EM, Z>(
|
||||||
|
&mut self,
|
||||||
|
fuzzer: &mut Z,
|
||||||
|
executor: &mut E,
|
||||||
|
generator: &mut G,
|
||||||
|
manager: &mut EM,
|
||||||
|
num: usize,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
G: Generator<I, R>,
|
||||||
|
Z: Evaluator<E, EM, I, Self>,
|
||||||
|
EM: EventFirer<I, Self>,
|
||||||
|
{
|
||||||
|
self.generate_initial_internal(fuzzer, executor, generator, manager, num, true)
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate `num` initial inputs, using the passed-in generator.
|
/// Generate `num` initial inputs, using the passed-in generator.
|
||||||
pub fn generate_initial_inputs<G, E, EM, Z>(
|
pub fn generate_initial_inputs<G, E, EM, Z>(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -481,23 +536,7 @@ where
|
|||||||
Z: Evaluator<E, EM, I, Self>,
|
Z: Evaluator<E, EM, I, Self>,
|
||||||
EM: EventFirer<I, Self>,
|
EM: EventFirer<I, Self>,
|
||||||
{
|
{
|
||||||
let mut added = 0;
|
self.generate_initial_internal(fuzzer, executor, generator, manager, num, false)
|
||||||
for _ in 0..num {
|
|
||||||
let input = generator.generate(self.rand_mut())?;
|
|
||||||
let (res, _) = fuzzer.evaluate_input(self, executor, manager, input)?;
|
|
||||||
if res != ExecuteInputResult::None {
|
|
||||||
added += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
manager.fire(
|
|
||||||
self,
|
|
||||||
Event::Log {
|
|
||||||
severity_level: LogSeverity::Debug,
|
|
||||||
message: format!("Loaded {} over {} initial testcases", added, num),
|
|
||||||
phantom: PhantomData,
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `State`, taking ownership of all of the individual components during fuzzing.
|
/// Creates a new `State`, taking ownership of all of the individual components during fuzzing.
|
||||||
|
27
libafl_qemu/Cargo.toml
Normal file
27
libafl_qemu/Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "libafl_qemu"
|
||||||
|
version = "0.5.0"
|
||||||
|
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>"]
|
||||||
|
description = "QEMU user backend library for LibAFL"
|
||||||
|
documentation = "https://docs.rs/libafl_qemu"
|
||||||
|
repository = "https://github.com/AFLplusplus/LibAFL/"
|
||||||
|
readme = "../README.md"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
keywords = ["fuzzing", "qemu", "instrumentation"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libafl = { path = "../libafl", version = "0.5.0" }
|
||||||
|
libafl_targets = { path = "../libafl_targets", version = "0.5.0" }
|
||||||
|
serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib
|
||||||
|
hashbrown = { version = "0.9", features = ["serde", "ahash-compile-time-rng"] } # A faster hashmap, nostd compatible
|
||||||
|
num = "0.4"
|
||||||
|
num_enum = "0.5.1"
|
||||||
|
goblin = "0.4.2"
|
||||||
|
libc = "0.2.97"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cc = { version = "1.0" }
|
21
libafl_qemu/build.rs
Normal file
21
libafl_qemu/build.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use std::{env, path::Path};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||||
|
let out_dir = out_dir.to_string_lossy().to_string();
|
||||||
|
let src_dir = Path::new("src");
|
||||||
|
|
||||||
|
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||||
|
|
||||||
|
if target_os == "linux" {
|
||||||
|
println!("cargo:rerun-if-changed=src/weaks.c");
|
||||||
|
|
||||||
|
cc::Build::new()
|
||||||
|
.file(src_dir.join("weaks.c"))
|
||||||
|
.compile("weaks");
|
||||||
|
|
||||||
|
println!("cargo:rustc-link-search=native={}", &out_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed=build.rs");
|
||||||
|
}
|
25
libafl_qemu/src/amd64.rs
Normal file
25
libafl_qemu/src/amd64.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
|
||||||
|
#[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy)]
|
||||||
|
#[repr(i32)]
|
||||||
|
#[allow(clippy::pub_enum_variant_names)]
|
||||||
|
pub enum Amd64Regs {
|
||||||
|
Rax = 0,
|
||||||
|
Rbx = 1,
|
||||||
|
Rcx = 2,
|
||||||
|
Rdx = 3,
|
||||||
|
Rsi = 4,
|
||||||
|
Rdi = 5,
|
||||||
|
Rbp = 6,
|
||||||
|
Rsp = 7,
|
||||||
|
R8 = 8,
|
||||||
|
R9 = 9,
|
||||||
|
R10 = 10,
|
||||||
|
R11 = 11,
|
||||||
|
R12 = 12,
|
||||||
|
R13 = 13,
|
||||||
|
R14 = 14,
|
||||||
|
R15 = 15,
|
||||||
|
Rip = 16,
|
||||||
|
Rflags = 17,
|
||||||
|
}
|
55
libafl_qemu/src/elf.rs
Normal file
55
libafl_qemu/src/elf.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
//! Utilities to parse and process ELFs
|
||||||
|
|
||||||
|
use goblin::elf::Elf;
|
||||||
|
use std::{convert::AsRef, fs::File, io::Read, path::Path, str};
|
||||||
|
|
||||||
|
use libafl::Error;
|
||||||
|
|
||||||
|
pub struct EasyElf<'a> {
|
||||||
|
elf: Elf<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> EasyElf<'a> {
|
||||||
|
pub fn from_file<P>(path: P, buffer: &'a mut Vec<u8>) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let elf = {
|
||||||
|
let mut binary_file = File::open(path)?;
|
||||||
|
binary_file.read_to_end(buffer)?;
|
||||||
|
Elf::parse(buffer).map_err(|e| Error::Unknown(format!("{}", e)))
|
||||||
|
}?;
|
||||||
|
Ok(Self { elf })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_slice(buffer: &'a [u8]) -> Result<Self, Error> {
|
||||||
|
let elf = Elf::parse(buffer).map_err(|e| Error::Unknown(format!("{}", e)))?;
|
||||||
|
Ok(Self { elf })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn goblin(&self) -> &Elf<'a> {
|
||||||
|
&self.elf
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn goblin_mut(&mut self) -> &mut Elf<'a> {
|
||||||
|
&mut self.elf
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn resolve_symbol(&self, name: &str) -> Option<u64> {
|
||||||
|
for sym in self.elf.syms.iter() {
|
||||||
|
if let Some(sym_name) = self.elf.strtab.get_at(sym.st_name) {
|
||||||
|
if sym_name == name {
|
||||||
|
return if sym.st_value == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(sym.st_value)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
172
libafl_qemu/src/emu.rs
Normal file
172
libafl_qemu/src/emu.rs
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
//! Expose QEMU user `LibAFL` C api to Rust
|
||||||
|
|
||||||
|
use core::{convert::Into, mem::transmute, ptr::copy_nonoverlapping};
|
||||||
|
use num::Num;
|
||||||
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
use std::{mem::size_of, slice::from_raw_parts, str::from_utf8_unchecked};
|
||||||
|
|
||||||
|
pub const SKIP_EXEC_HOOK: u32 = u32::MAX;
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
fn libafl_qemu_write_reg(reg: i32, val: *const u8) -> i32;
|
||||||
|
fn libafl_qemu_read_reg(reg: i32, val: *mut u8) -> i32;
|
||||||
|
fn libafl_qemu_num_regs() -> i32;
|
||||||
|
fn libafl_qemu_set_breakpoint(addr: u64) -> i32;
|
||||||
|
fn libafl_qemu_remove_breakpoint(addr: u64) -> i32;
|
||||||
|
fn libafl_qemu_run() -> i32;
|
||||||
|
|
||||||
|
fn strlen(s: *const u8) -> usize;
|
||||||
|
|
||||||
|
/// abi_long target_mmap(abi_ulong start, abi_ulong len, int target_prot, int flags, int fd, abi_ulong offset)
|
||||||
|
fn target_mmap(start: u64, len: u64, target_prot: i32, flags: i32, fd: i32, offset: u64)
|
||||||
|
-> u64;
|
||||||
|
|
||||||
|
/// int target_munmap(abi_ulong start, abi_ulong len)
|
||||||
|
fn target_munmap(start: u64, len: u64) -> i32;
|
||||||
|
|
||||||
|
static exec_path: *const u8;
|
||||||
|
static guest_base: usize;
|
||||||
|
|
||||||
|
static mut libafl_exec_edge_hook: unsafe extern "C" fn(u32);
|
||||||
|
static mut libafl_gen_edge_hook: unsafe extern "C" fn(u64, u64) -> u32;
|
||||||
|
static mut libafl_exec_block_hook: unsafe extern "C" fn(u64);
|
||||||
|
static mut libafl_gen_block_hook: unsafe extern "C" fn(u64) -> u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy)]
|
||||||
|
#[repr(i32)]
|
||||||
|
#[allow(clippy::pub_enum_variant_names)]
|
||||||
|
pub enum MmapPerms {
|
||||||
|
Read = libc::PROT_READ,
|
||||||
|
Write = libc::PROT_WRITE,
|
||||||
|
Execute = libc::PROT_EXEC,
|
||||||
|
ReadWrite = libc::PROT_READ | libc::PROT_WRITE,
|
||||||
|
ReadExecute = libc::PROT_READ | libc::PROT_EXEC,
|
||||||
|
WriteExecute = libc::PROT_WRITE | libc::PROT_EXEC,
|
||||||
|
ReadWriteExecute = libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_mem<T>(addr: u64, buf: &[T]) {
|
||||||
|
let host_addr = g2h(addr);
|
||||||
|
unsafe {
|
||||||
|
copy_nonoverlapping(
|
||||||
|
buf.as_ptr() as *const _ as *const u8,
|
||||||
|
host_addr,
|
||||||
|
buf.len() * size_of::<T>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_mem<T>(addr: u64, buf: &mut [T]) {
|
||||||
|
let host_addr = g2h(addr);
|
||||||
|
unsafe {
|
||||||
|
copy_nonoverlapping(
|
||||||
|
host_addr as *const u8,
|
||||||
|
buf.as_mut_ptr() as *mut _ as *mut u8,
|
||||||
|
buf.len() * size_of::<T>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn num_regs() -> i32 {
|
||||||
|
unsafe { libafl_qemu_num_regs() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_reg<R, T>(reg: R, val: T) -> Result<(), String>
|
||||||
|
where
|
||||||
|
T: Num + PartialOrd + Copy,
|
||||||
|
R: Into<i32>,
|
||||||
|
{
|
||||||
|
let reg = reg.into();
|
||||||
|
let success = unsafe { libafl_qemu_write_reg(reg, &val as *const _ as *const u8) };
|
||||||
|
if success == 0 {
|
||||||
|
Err(format!("Failed to write to register {}", reg))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_reg<R, T>(reg: R) -> Result<T, String>
|
||||||
|
where
|
||||||
|
T: Num + PartialOrd + Copy,
|
||||||
|
R: Into<i32>,
|
||||||
|
{
|
||||||
|
let reg = reg.into();
|
||||||
|
let mut val = T::zero();
|
||||||
|
let success = unsafe { libafl_qemu_read_reg(reg, &mut val as *mut _ as *mut u8) };
|
||||||
|
if success == 0 {
|
||||||
|
Err(format!("Failed to read register {}", reg))
|
||||||
|
} else {
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_breakpoint(addr: u64) {
|
||||||
|
unsafe { libafl_qemu_set_breakpoint(addr) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_breakpoint(addr: u64) {
|
||||||
|
unsafe { libafl_qemu_remove_breakpoint(addr) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run() {
|
||||||
|
unsafe { libafl_qemu_run() };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn g2h<T>(addr: u64) -> *mut T {
|
||||||
|
unsafe { transmute(addr + guest_base as u64) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn h2g<T>(addr: *const T) -> u64 {
|
||||||
|
unsafe { (addr as usize - guest_base) as u64 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn binary_path<'a>() -> &'a str {
|
||||||
|
unsafe { from_utf8_unchecked(from_raw_parts(exec_path, strlen(exec_path))) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_private(addr: u64, size: usize, perms: MmapPerms) -> Result<u64, String> {
|
||||||
|
let res = unsafe {
|
||||||
|
target_mmap(
|
||||||
|
addr,
|
||||||
|
size as u64,
|
||||||
|
perms.into(),
|
||||||
|
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if res == 0 {
|
||||||
|
Err(format!("Failed to map {}", addr))
|
||||||
|
} else {
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmap(addr: u64, size: usize) -> Result<(), String> {
|
||||||
|
if unsafe { target_munmap(addr, size as u64) } == 0 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("Failed to unmap {}", addr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_exec_edge_hook(hook: extern "C" fn(u32)) {
|
||||||
|
unsafe { libafl_exec_edge_hook = hook };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_gen_edge_hook(hook: extern "C" fn(u64, u64) -> u32) {
|
||||||
|
unsafe { libafl_gen_edge_hook = hook };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_exec_block_hook(hook: extern "C" fn(u64)) {
|
||||||
|
unsafe { libafl_exec_block_hook = hook };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_gen_block_hook(hook: extern "C" fn(u64) -> u32) {
|
||||||
|
unsafe { libafl_gen_block_hook = hook };
|
||||||
|
}
|
125
libafl_qemu/src/executor.rs
Normal file
125
libafl_qemu/src/executor.rs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
use core::{ffi::c_void, mem::transmute, ptr};
|
||||||
|
|
||||||
|
use libafl::{
|
||||||
|
corpus::Corpus,
|
||||||
|
events::{EventFirer, EventRestarter},
|
||||||
|
executors::{inprocess::GLOBAL_STATE, Executor, ExitKind, HasObservers, InProcessExecutor},
|
||||||
|
feedbacks::Feedback,
|
||||||
|
fuzzer::HasObjective,
|
||||||
|
inputs::Input,
|
||||||
|
observers::ObserversTuple,
|
||||||
|
state::{HasClientPerfStats, HasSolutions},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{emu, emu::SKIP_EXEC_HOOK};
|
||||||
|
|
||||||
|
static mut GEN_EDGE_HOOK_PTR: *const c_void = ptr::null();
|
||||||
|
static mut GEN_BLOCK_HOOK_PTR: *const c_void = ptr::null();
|
||||||
|
|
||||||
|
extern "C" fn gen_edge_hook_wrapper<S>(src: u64, dst: u64) -> u32 {
|
||||||
|
unsafe {
|
||||||
|
let state = (GLOBAL_STATE.state_ptr as *mut S).as_mut().unwrap();
|
||||||
|
let func: fn(&mut S, u64, u64) -> Option<u32> = transmute(GEN_EDGE_HOOK_PTR);
|
||||||
|
(func)(state, src, dst).map_or(SKIP_EXEC_HOOK, |id| id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn gen_block_hook_wrapper<S>(addr: u64) -> u32 {
|
||||||
|
unsafe {
|
||||||
|
let state = (GLOBAL_STATE.state_ptr as *mut S).as_mut().unwrap();
|
||||||
|
let func: fn(&mut S, u64) -> Option<u32> = transmute(GEN_BLOCK_HOOK_PTR);
|
||||||
|
(func)(state, addr).map_or(SKIP_EXEC_HOOK, |id| id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct QemuExecutor<'a, H, I, OT, S>
|
||||||
|
where
|
||||||
|
H: FnMut(&I) -> ExitKind,
|
||||||
|
I: Input,
|
||||||
|
OT: ObserversTuple<I, S>,
|
||||||
|
{
|
||||||
|
inner: InProcessExecutor<'a, H, I, OT, S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, H, I, OT, S> QemuExecutor<'a, H, I, OT, S>
|
||||||
|
where
|
||||||
|
H: FnMut(&I) -> ExitKind,
|
||||||
|
I: Input,
|
||||||
|
OT: ObserversTuple<I, S>,
|
||||||
|
{
|
||||||
|
pub fn new<EM, OC, OF, Z>(
|
||||||
|
harness_fn: &'a mut H,
|
||||||
|
observers: OT,
|
||||||
|
fuzzer: &mut Z,
|
||||||
|
state: &mut S,
|
||||||
|
event_mgr: &mut EM,
|
||||||
|
) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
EM: EventFirer<I, S> + EventRestarter<S>,
|
||||||
|
OC: Corpus<I>,
|
||||||
|
OF: Feedback<I, S>,
|
||||||
|
S: HasSolutions<OC, I> + HasClientPerfStats,
|
||||||
|
Z: HasObjective<I, OF, S>,
|
||||||
|
{
|
||||||
|
Ok(Self {
|
||||||
|
inner: InProcessExecutor::new(harness_fn, observers, fuzzer, state, event_mgr)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unused_self)]
|
||||||
|
pub fn hook_edge_generation(&self, hook: fn(&mut S, u64, u64) -> Option<u32>) {
|
||||||
|
unsafe { GEN_EDGE_HOOK_PTR = hook as *const _ };
|
||||||
|
emu::set_gen_edge_hook(gen_edge_hook_wrapper::<S>);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unused_self)]
|
||||||
|
pub fn hook_edge_execution(&self, hook: extern "C" fn(u32)) {
|
||||||
|
emu::set_exec_edge_hook(hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unused_self)]
|
||||||
|
pub fn hook_block_generation(&self, hook: fn(&mut S, u64) -> Option<u32>) {
|
||||||
|
unsafe { GEN_BLOCK_HOOK_PTR = hook as *const _ };
|
||||||
|
emu::set_gen_block_hook(gen_block_hook_wrapper::<S>);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unused_self)]
|
||||||
|
pub fn hook_block_execution(&self, hook: extern "C" fn(u64)) {
|
||||||
|
emu::set_exec_block_hook(hook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, EM, H, I, OT, S, Z> Executor<EM, I, S, Z> for QemuExecutor<'a, H, I, OT, S>
|
||||||
|
where
|
||||||
|
H: FnMut(&I) -> ExitKind,
|
||||||
|
I: Input,
|
||||||
|
OT: ObserversTuple<I, S>,
|
||||||
|
{
|
||||||
|
fn run_target(
|
||||||
|
&mut self,
|
||||||
|
fuzzer: &mut Z,
|
||||||
|
state: &mut S,
|
||||||
|
mgr: &mut EM,
|
||||||
|
input: &I,
|
||||||
|
) -> Result<ExitKind, Error> {
|
||||||
|
self.inner.run_target(fuzzer, state, mgr, input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, H, I, OT, S> HasObservers<I, OT, S> for QemuExecutor<'a, H, I, OT, S>
|
||||||
|
where
|
||||||
|
H: FnMut(&I) -> ExitKind,
|
||||||
|
I: Input,
|
||||||
|
OT: ObserversTuple<I, S>,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn observers(&self) -> &OT {
|
||||||
|
self.inner.observers()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn observers_mut(&mut self) -> &mut OT {
|
||||||
|
self.inner.observers_mut()
|
||||||
|
}
|
||||||
|
}
|
48
libafl_qemu/src/hooks.rs
Normal file
48
libafl_qemu/src/hooks.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
use hashbrown::HashMap;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use libafl::state::HasMetadata;
|
||||||
|
pub use libafl_targets::{EDGES_MAP, EDGES_MAP_SIZE, MAX_EDGES_NUM};
|
||||||
|
|
||||||
|
/// A testcase metadata saying if a testcase is favored
|
||||||
|
#[derive(Default, Serialize, Deserialize)]
|
||||||
|
pub struct QemuEdgesMapMetadata {
|
||||||
|
pub map: HashMap<(u64, u64), u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QemuEdgesMapMetadata {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
map: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
libafl::impl_serdeany!(QemuEdgesMapMetadata);
|
||||||
|
|
||||||
|
pub fn gen_unique_edges_id<S>(state: &mut S, src: u64, dest: u64) -> Option<u32>
|
||||||
|
where
|
||||||
|
S: HasMetadata,
|
||||||
|
{
|
||||||
|
if state.metadata().get::<QemuEdgesMapMetadata>().is_none() {
|
||||||
|
state.add_metadata(QemuEdgesMapMetadata::new());
|
||||||
|
}
|
||||||
|
let meta = state
|
||||||
|
.metadata_mut()
|
||||||
|
.get_mut::<QemuEdgesMapMetadata>()
|
||||||
|
.unwrap();
|
||||||
|
Some(*meta.map.entry((src, dest)).or_insert_with(|| unsafe {
|
||||||
|
let id = MAX_EDGES_NUM;
|
||||||
|
MAX_EDGES_NUM = (MAX_EDGES_NUM + 1) & (EDGES_MAP_SIZE - 1);
|
||||||
|
id as u32
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "C" fn exec_log_hitcount(id: u32) {
|
||||||
|
unsafe { EDGES_MAP[id as usize] += 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "C" fn exec_log_single(id: u32) {
|
||||||
|
unsafe { EDGES_MAP[id as usize] = 1 };
|
||||||
|
}
|
34
libafl_qemu/src/lib.rs
Normal file
34
libafl_qemu/src/lib.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use std::env;
|
||||||
|
|
||||||
|
pub mod amd64;
|
||||||
|
pub mod x86;
|
||||||
|
|
||||||
|
pub mod elf;
|
||||||
|
pub mod hooks;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub mod executor;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub use executor::QemuExecutor;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub mod emu;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub use emu::*;
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn filter_qemu_args() -> Vec<String> {
|
||||||
|
let mut args = vec![env::args().next().unwrap()];
|
||||||
|
let mut args_iter = env::args();
|
||||||
|
|
||||||
|
while let Some(arg) = args_iter.next() {
|
||||||
|
if arg.starts_with("--libafl") {
|
||||||
|
args.push(arg);
|
||||||
|
args.push(args_iter.next().unwrap());
|
||||||
|
} else if arg.starts_with("-libafl") {
|
||||||
|
args.push("-".to_owned() + &arg);
|
||||||
|
args.push(args_iter.next().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args
|
||||||
|
}
|
66
libafl_qemu/src/weaks.c
Normal file
66
libafl_qemu/src/weaks.c
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef int64_t abi_long;
|
||||||
|
typedef uint64_t abi_ulong;
|
||||||
|
|
||||||
|
__attribute__((weak)) int libafl_qemu_write_reg(int reg, uint8_t* val) {
|
||||||
|
(void)reg;
|
||||||
|
(void)val;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((weak)) int libafl_qemu_read_reg(int reg, uint8_t* val) {
|
||||||
|
(void)reg;
|
||||||
|
(void)val;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((weak)) int libafl_qemu_num_regs(void) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((weak)) int libafl_qemu_set_breakpoint(uint64_t addr) {
|
||||||
|
(void)addr;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((weak)) int libafl_qemu_remove_breakpoint(uint64_t addr) {
|
||||||
|
(void)addr;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((weak)) int libafl_qemu_run() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((weak)) uint64_t libafl_load_addr() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((weak)) abi_long target_mmap(abi_ulong start, abi_ulong len,
|
||||||
|
int target_prot, int flags, int fd,
|
||||||
|
abi_ulong offset) {
|
||||||
|
|
||||||
|
(void)start;
|
||||||
|
(void)len;
|
||||||
|
(void)target_prot;
|
||||||
|
(void)flags;
|
||||||
|
(void)fd;
|
||||||
|
(void)offset;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((weak)) int target_munmap(abi_ulong start, abi_ulong len) {
|
||||||
|
(void)start;
|
||||||
|
(void)len;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((weak)) char* exec_path = NULL;
|
||||||
|
__attribute__((weak)) size_t guest_base = 0;
|
||||||
|
|
||||||
|
__attribute__((weak)) void (*libafl_exec_edge_hook)(uint32_t);
|
||||||
|
__attribute__((weak)) uint32_t (*libafl_gen_edge_hook)(uint64_t, uint64_t);
|
||||||
|
__attribute__((weak)) void (*libafl_exec_block_hook)(uint64_t);
|
||||||
|
__attribute__((weak)) uint32_t (*libafl_gen_block_hook)(uint64_t);
|
17
libafl_qemu/src/x86.rs
Normal file
17
libafl_qemu/src/x86.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
|
||||||
|
#[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy)]
|
||||||
|
#[repr(i32)]
|
||||||
|
#[allow(clippy::pub_enum_variant_names)]
|
||||||
|
pub enum X86Regs {
|
||||||
|
Eax = 0,
|
||||||
|
Ebx = 1,
|
||||||
|
Ecx = 2,
|
||||||
|
Edx = 3,
|
||||||
|
Esi = 4,
|
||||||
|
Edi = 5,
|
||||||
|
Ebp = 6,
|
||||||
|
Esp = 7,
|
||||||
|
Eip = 8,
|
||||||
|
Eflags = 9,
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user