Skip to content
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

Example showing the compatibility layer between AFL++ and LibAFL QEMU usermode #1983

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
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
18 changes: 18 additions & 0 deletions fuzzers/aflpp_libafl_qemu/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "aflpp_libafl_qemu"
version = "0.11.2"
authors = ["Andrea Fioraldi <[email protected]>"]
edition = "2021"

[profile.release]
lto = true
codegen-units = 1
opt-level = 3
debug = true

[dependencies]
libafl = { path = "../../libafl/" }
libafl_bolts = { path = "../../libafl_bolts/" }
libafl_targets = { path = "../../libafl_targets/", features = ["pointer_maps"] }
libafl_qemu = { path = "../../libafl_qemu/", features = ["usermode"] }
nix = { version = "0.27", default-features = false } # match libafl_bolts version
1 change: 1 addition & 0 deletions fuzzers/aflpp_libafl_qemu/corpus/0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
00000
113 changes: 113 additions & 0 deletions fuzzers/aflpp_libafl_qemu/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use libafl::prelude::*;
use libafl_bolts::prelude::*;
use libafl_qemu::{
asan::{ QemuAsanHelper, QemuAsanOptions},
cmplog::{CmpLogObserver, QemuCmpLogHelper, QemuCmpLogRoutinesHelper},
calls::{QemuCallTracerHelper, FullBacktraceCollector},
edges::{edges_map_mut_slice, std_edges_map_observer, QemuEdgeCoverageClassicHelper, QemuEdgeCoverageHelper, MAX_EDGES_NUM},
elf::EasyElf,
emu::Emulator,
helper::{QemuFilterList, HasInstrumentationFilter, QemuHelper, QemuHelperTuple},
hooks::QemuHooks,
snapshot::QemuSnapshotHelper,
QemuExecutor, Regs, SYS_close, SYS_faccessat, SYS_lseek, SYS_newfstatat, SYS_openat, SYS_read,
SYS_rt_sigprocmask, SYS_write, SyscallHookResult,
};
use libafl_targets::forkserver::{ForkserverHooks, start_forkserver_with_hooks, map_shared_memory};
use std::env;
use nix::unistd::{close, pipe, read, write};
use nix::libc;

const TSL_FD: std::os::fd::RawFd = 196;

struct TSLHooks {
read_end: std::os::fd::RawFd,
}

impl TSLHooks {
fn new() -> Self {
Self { read_end: 0 }
}

fn wait_tsl(&mut self) -> Result<(), ()> {
Ok(())
}
}

impl ForkserverHooks for TSLHooks {
extern "C" fn pre_fork(&mut self) {
// Establish a channel with child to grab translation commands.
// We'll read from the pipe read end, child will write to TSL_FD.
let (read_end, write_end) = pipe().expect("Could not create TSL pipe");
self.read_end = read_end;
dup2(write_end, TSL_FD).expect("Could not dup2 the TSL pipe write end");
close(write_end);
}
extern "C" fn post_fork(&mut self, pid: libc::pid_t) {
if pid != 0 {
// Parent
close(TSL_FD);
} else {
// Child
close(self.read_end);
}
}
extern "C" fn iter_start(&mut self) {
self.wait_tsl().expect("Wait for TSL failed");
}
extern "C" fn iter_end(&mut self, status: i32) {}
}

fn main() {
let in_afl = env::var("__AFL_SHM_ID").is_ok();

// Initialize QEMU
let mut args: Vec<String> = env::args().collect();
let mut env: Vec<(String, String)> = env::vars().collect();

//let (emu, asan) = init_with_asan(&mut args, &mut env).unwrap();
let emu = Emulator::new(&mut args, &mut env).unwrap();

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

let entry_point = elf
.entry_point(emu.load_addr())
.expect("Entry point not found");

// Break at the entry point after the loading process
emu.set_breakpoint(entry_point);
unsafe { emu.run() };
emu.remove_breakpoint(entry_point);

// Now that the libs are loaded, build the intrumentation filter
let mut allow_list = vec![];
for region in emu.mappings() {
if let Some(path) = region.path() {
if path.contains(emu.binary_path()) {
allow_list.push(region.start()..region.end());
// println!("Instrument {:?} {:#x}-{:#x}", path, region.start(), region.end());
}
}
}

if in_afl { map_shared_memory(); }

let mut hooks = QemuHooks::reproducer(
emu.clone(),
tuple_list!(
QemuEdgeCoverageClassicHelper::new(QemuFilterList::None)
),
);

let input = BytesInput::new(vec![]);

let mut test_harness = |_: &BytesInput| {
unsafe { emu.run() };
ExitKind::Ok
};

if in_afl { start_forkserver(); }

hooks.repro_run(&mut test_harness, &input);
}
35 changes: 32 additions & 3 deletions libafl_targets/src/forkserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ uint8_t *__afl_fuzz_ptr;
static uint32_t __afl_fuzz_len_local;
uint32_t *__afl_fuzz_len = &__afl_fuzz_len_local;

int already_initialized_shm;
int already_initialized_forkserver;
static int already_initialized_shm;
static int already_initialized_forkserver;

static int child_pid;
static void (*old_sigterm_handler)(int) = 0;
Expand Down Expand Up @@ -210,7 +210,15 @@ static void map_input_shared_memory() {

/* Fork server logic. */

void __afl_start_forkserver(void) {
struct libafl_forkserver_hooks {
void *data;
void (*pre_fork_hook)(void* data);
void (*post_fork_hook)(void* data, pid_t child_pid);
void (*iteration_start_hook)(void* data);
void (*iteration_end_hook)(void* data, int status);
};

void __libafl_start_forkserver(struct libafl_forkserver_hooks* hooks) {
if (already_initialized_forkserver) return;
already_initialized_forkserver = 1;

Expand Down Expand Up @@ -312,6 +320,11 @@ void __afl_start_forkserver(void) {
}

if (!child_stopped) {

if (hooks && hooks->pre_fork_hook) {
hooks->pre_fork_hook(hooks->data);
}

/* Once woken up, create a clone of our process. */

child_pid = fork();
Expand All @@ -320,6 +333,10 @@ void __afl_start_forkserver(void) {
_exit(1);
}

if (hooks && hooks->post_fork_hook) {
hooks->post_fork_hook(hooks->data, child_pid);
}

/* In child process: close fds, resume execution. */

if (!child_pid) {
Expand Down Expand Up @@ -347,6 +364,10 @@ void __afl_start_forkserver(void) {
write_error("write to afl-fuzz");
_exit(1);
}

if (hooks && hooks->iteration_start_hook) {
hooks->iteration_start_hook(hooks->data);
}

if (waitpid(child_pid, &status, is_persistent ? WUNTRACED : 0) < 0) {
write_error("waitpid");
Expand All @@ -359,6 +380,10 @@ void __afl_start_forkserver(void) {

if (WIFSTOPPED(status)) child_stopped = 1;

if (hooks && hooks->iteration_end_hook) {
hooks->iteration_end_hook(hooks->data, status);
}

/* Relay wait status to pipe, then loop back. */

if (write(FORKSRV_FD + 1, &status, 4) != 4) {
Expand All @@ -367,3 +392,7 @@ void __afl_start_forkserver(void) {
}
}
}

void __afl_start_forkserver(void) {
__libafl_start_forkserver(NULL);
}
58 changes: 58 additions & 0 deletions libafl_targets/src/forkserver.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
//! Forkserver logic into targets

/// Trait to implement hooks executed in the forkserver
pub trait ForkserverHooks {
/// Execute before the fork that creates the child
extern "C" fn pre_fork(&mut self);
/// Executed after the fork (pid == 0 means child process)
extern "C" fn post_fork(&mut self, pid: libc::pid_t);
/// Execute in the parent after the iteration start in the child
extern "C" fn iter_start(&mut self);
/// Execute in the parent after the iteration end (by waitpid) in the child
extern "C" fn iter_end(&mut self, status: i32);
}

#[repr(C)]
struct CForkserverHooks<H: ForkserverHooks> {
data: *mut H,
pre_fork_hook: extern "C" fn(*mut H),
post_fork_hook: extern "C" fn(*mut H, libc::pid_t),
iteration_start_hook: extern "C" fn(*mut H),
iteration_end_hook: extern "C" fn(*mut H, i32),
}

extern "C" {
/// Map a shared memory region for the edge coverage map.
fn __afl_map_shm();
/// Start the forkserver.
fn __afl_start_forkserver();
/// Start the forkserver with the hooks.
fn __libafl_start_forkserver(hooks: *mut ());
/// Set persistent mode
fn __afl_set_persistent_mode(mode: u8);
}

/// Map a shared memory region for the edge coverage map.
Expand All @@ -24,3 +49,36 @@ pub fn map_shared_memory() {
pub fn start_forkserver() {
unsafe { __afl_start_forkserver() }
}

/// Start the forkserver from this point. Any shared memory must be created before.
/// You must specify an object of a type implementing `ForkserverHooks`.
///
/// # Note
///
/// The forkserver logic is written in C and this code is a wrapper.
pub fn start_forkserver_with_hooks<H: ForkserverHooks>(hooks: &mut H) {
unsafe {
let mut c = CForkserverHooks {
data: hooks,
pre_fork_hook: core::mem::transmute(H::pre_fork as extern "C" fn(&mut H)),
post_fork_hook: core::mem::transmute(
H::post_fork as extern "C" fn(&mut H, libc::pid_t),
),
iteration_start_hook: core::mem::transmute(H::iter_start as extern "C" fn(&mut H)),
iteration_end_hook: core::mem::transmute(H::iter_end as extern "C" fn(&mut H, i32)),
};

__libafl_start_forkserver(core::mem::transmute((&mut c) as *mut _))
}
}

/// Set the forkserver to persistent mode.
///
/// # Note
///
/// This sets a global behaviour
pub fn set_persistent_mode() {
unsafe {
__afl_set_persistent_mode(1);
}
}
Loading