Skip to content

LibAFL QEMU forkserver compatible with AFL++ #3114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
@@ -270,6 +270,7 @@ jobs:
- ./fuzzers/binary_only/fuzzbench_qemu
- ./fuzzers/binary_only/intel_pt_baby_fuzzer
- ./fuzzers/binary_only/intel_pt_command_executor
# - ./fuzzers/binary_only/libafl_qemu_trace
- ./fuzzers/binary_only/tinyinst_simple

# Forkserver
7 changes: 3 additions & 4 deletions fuzzers/binary_only/fuzzbench_fork_qemu/Justfile
Original file line number Diff line number Diff line change
@@ -17,11 +17,10 @@ build:

[unix]
run: build harness
cargo run \
--profile {{ PROFILE }} \
{{ FUZZER }} \
{{ BUILD_DIR }}/harness \
-- \
--libafl-in ../../inprocess/libfuzzer_libpng/corpus \
--libafl-in ./corpus \
--libafl-out ./out

[unix]
@@ -39,4 +38,4 @@ test: build harness

[unix]
clean:
cargo clean
cargo clean
24 changes: 24 additions & 0 deletions fuzzers/binary_only/libafl_qemu_trace/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "libafl-qemu-trace"
authors = [
"Romain Malmain <romain.malmain@pm.me>",
"Andrea Fioraldi <andreafioraldi@gmail.com>",
]
edition = "2024"
version = "0.0.1"

[dependencies]
libafl = { path = "../../../libafl" }
libafl_bolts = { path = "../../../libafl_bolts" }
libafl_targets = { path = "../../../libafl_targets" }
libafl_qemu = { path = "../../../libafl_qemu" }
libafl_qemu_sys = { path = "../../../libafl_qemu/libafl_qemu_sys" }

env_logger = "0.11.7"
nix = { version = "0.29.0", features = ["signal"] }

[profile.release]
lto = "fat"
codegen-units = 1
opt-level = 3
debug = true
7 changes: 7 additions & 0 deletions fuzzers/binary_only/libafl_qemu_trace/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# LibAFL QEMU Trace

An adaptation of AFL++'s [qemuafl](https://github.com/AFLplusplus/qemuafl/).

## Supported environment variables

-
179 changes: 179 additions & 0 deletions fuzzers/binary_only/libafl_qemu_trace/src/env_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use core::fmt;
use std::{ops::Range, str::FromStr, sync::LazyLock};

use libafl_qemu::GuestAddr;

macro_rules! def_env {
(
$(#[$meta:meta])*
$env_name:ident, $env_ty:ty, $converter_func:path
) => {
$(#[$meta])*
pub static $env_name: std::sync::LazyLock<Option<$env_ty>> = LazyLock::new(|| {
let res = std::env::var(stringify!($env_name));

match res {
Ok(val) => Some($converter_func(val.to_string()).unwrap()),
Err(std::env::VarError::NotPresent) => None,
_ => panic!("Error while fetching env variable: {res:?}"),
}
});
}
}

fn env_bool(s: String) -> Result<bool, String> {
match s.as_str() {
"0" => Ok(false),
"1" => Ok(true),
_ => Err(format!("Couldn't convert {s} into a boolean.")),
}
}

fn env_ranges<T>(s: String) -> Result<Vec<Range<T>>, String>
where
T: FromStr,
<T as FromStr>::Err: fmt::Display,
{
let ranges_to_parse = s.split(",");

let mut ranges: Vec<Range<T>> = Vec::new();

for range_str in ranges_to_parse {
let mut range_bounds = range_str.split("-");

if range_bounds.clone().count() != 2 {
return Err(format!("Invalid range syntax: {range_str}"));
}

let start_addr = range_bounds
.next()
.unwrap()
.parse::<T>()
.map_err(|e| format!("Couldn't parse range start address: {e}"))?;

let end_addr = range_bounds
.next()
.unwrap()
.parse::<T>()
.map_err(|e| format!("Couldn't parse range end address: {e}"))?;

ranges.push(start_addr..end_addr);
}

Ok(ranges)
}

fn env_val<T>(s: String) -> Result<T, String>
where
T: FromStr,
<T as FromStr>::Err: fmt::Display,
{
s.parse::<T>().map_err(|e| e.to_string())
}

macro_rules! def_env_bool {
(
$(#[$meta:meta])*
$env_name:ident
) => {
$(#[$meta])*
def_env!($env_name, bool, env_bool);
}
}

macro_rules! def_env_u32 {
(
$(#[$meta:meta])*
$env_name:ident
) => {
$(#[$meta])*
def_env!($env_name, u32, env_val::<u32>);
}
}

macro_rules! def_env_addr {
(
$(#[$meta:meta])*
$env_name:ident
) => {
$(#[$meta])*
def_env!($env_name, GuestAddr, env_val::<GuestAddr>);
}
}

macro_rules! def_env_addr_ranges {
(
$(#[$meta:meta])*
$env_name:ident
) => {
$(#[$meta])*
def_env!($env_name, Vec<Range<GuestAddr>>, env_ranges::<GuestAddr>);
}
}

def_env_addr!(AFL_ENTRYPOINT);
def_env_u32!(AFL_INST_RATIO);
def_env_u32!(__AFL_SHM_ID);
def_env_u32!(__AFL_SHM_FUZZ_ID);

/// CmpLog map SHM ID
def_env_u32!(__AFL_CMPLOG_SHM_ID);

/// Asan SHM ID
def_env_u32!(__AFL_ASAN_SHM_ID);

/// Disable TB caching
def_env_bool!(AFL_QEMU_DISABLE_CACHE);

/// Enable cmplog forkserver
def_env_bool!(___AFL_EINS_ZWEI_POLIZEI___);

def_env_bool!(AFL_INST_LIBS);

/// Set code start vaddr manually
def_env_addr!(AFL_CODE_START);

/// Set code end vaddr manually
def_env_addr!(AFL_CODE_END);

/// Set instrumentation ranges
///
/// Format:
/// 0xaaaa-0xbbbb,0xcccc-0xdddd
def_env_addr_ranges!(AFL_QEMU_INST_RANGES);

/// Exclude instrumentation ranges
///
/// Format is same as [`AFL_QEMU_INST_RANGES`]
def_env_addr_ranges!(AFL_QEMU_EXCLUDE_RANGES);

/// Enable debugging
def_env_bool!(AFL_DEBUG);

def_env_bool!(AFL_QEMU_COMPCOV);

def_env_u32!(AFL_COMPCOV_LEVEL);

def_env_bool!(AFL_QEMU_FORCE_DFL);

def_env_addr!(AFL_QEMU_PERSISTENT_ADDR);

def_env_addr!(AFL_QEMU_PERSISTENT_RET);

def_env_bool!(AFL_QEMU_PERSISTENT_HOOK);

def_env_bool!(AFL_QEMU_PERSISTENT_MEM);

def_env_bool!(AFL_QEMU_PERSISTENT_GPR);

def_env_u32!(AFL_QEMU_PERSISTENT_CNT);

def_env_bool!(AFL_QEMU_PERSISTENT_EXITS);

def_env_bool!(AFL_QEMU_SNAPSHOT);

def_env_addr!(AFL_QEMU_PERSISTENT_RETADDR_OFFSET);

def_env_bool!(AFL_USE_QASAN);

def_env_bool!(AFL_NO_CRASH_README);
117 changes: 117 additions & 0 deletions fuzzers/binary_only/libafl_qemu_trace/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//! An AFL++ compatible afl-qemu-trace alternative using LibAFL QEMU as a backend.

use std::{env, ffi::c_void};

use libafl::{
executors::hooks::inprocess::GLOBAL_STATE,
inputs::{BytesInput, ValueInput},
observers::ConstBytesMap,
state::NopState,
};
use libafl_bolts::tuples::tuple_list;
use libafl_qemu::{
Emulator,
command::NopCommand,
elf::EasyElf,
modules::{edges::StdEdgeCoverageClassicModule, utils::filters::HasAddressFilterTuple},
};
use libafl_targets::{__afl_map_size, EDGES_MAP_DEFAULT_SIZE, EDGES_MAP_PTR, Forkserver};
use tsl::{TSLForkserverHook, TSLModule};

mod env_config;
mod tsl;

/// The fuzzer main
pub fn main() {
env_logger::init();

let args: Vec<String> = env::args().collect();

let mut fs = Forkserver::new(TSLForkserverHook::new());

// Share afl map
unsafe {
fs.map_shared_memory();
}

// For now, we consider the map size to be constant
unsafe {
assert_eq!(EDGES_MAP_DEFAULT_SIZE, *&raw const __afl_map_size);
}

let mut const_map = unsafe { ConstBytesMap::<EDGES_MAP_DEFAULT_SIZE>::new(EDGES_MAP_PTR) };

unsafe {
println!("map addr: {:#x}", EDGES_MAP_PTR as u64);
}

let modules = tuple_list!(
StdEdgeCoverageClassicModule::builder()
.const_map(&mut const_map)
.jit(false)
.hitcounts(true)
.build()
.unwrap(),
TSLModule::new(),
);

let mut emulator =
Emulator::<NopCommand, _, _, _, BytesInput, NopState<BytesInput>, _>::empty()
.qemu_parameters(&args)
.modules(modules)
.build()
.unwrap();

let mut state = NopState::<BytesInput>::new();

unsafe {
GLOBAL_STATE.state_ptr = &raw mut state as *mut c_void;
}

let qemu = emulator.qemu();

let mut elf_buffer = Vec::new();
let elf = EasyElf::from_file(emulator.binary_path(), &mut elf_buffer).unwrap();

let entry_point = env_config::AFL_ENTRYPOINT.unwrap_or_else(|| {
elf.entry_point(emulator.load_addr())
.expect("Could not find the default entr point")
});

qemu.set_breakpoint(entry_point);
unsafe { qemu.run().unwrap() };
qemu.remove_breakpoint(entry_point);

emulator.first_exec(&mut state);

// Now that the libs are loaded, build the intrumentation filter
for region in emulator.mappings() {
if let Some(path) = region.path() {
if path.contains(emulator.binary_path()) {
let range = region.start()..region.end();
emulator
.modules_mut()
.modules_mut()
.allow_address_range_all(&range);
}
}
}

unsafe {
emulator.run_target_crash_hooks_on_dying_signal();
}

let dummy_input = ValueInput::from(vec![]);
emulator.pre_exec(&mut state, &dummy_input);

unsafe {
fs.start_forkserver().unwrap();
}

unsafe {
qemu.run().unwrap();
}

// QEMU should exit by itself
unreachable!();
}
Loading