From f59fd22f372bb173282cc33923c7a9673ca59ab1 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Thu, 27 Mar 2025 16:28:32 +0100 Subject: [PATCH 1/3] working libafl qemu forkserver with tsl --- .../fuzzbench_fork_qemu/Cargo.lock | 63 ++++ .../binary_only/fuzzbench_fork_qemu/Justfile | 13 +- .../binary_only/libafl_qemu_trace/Cargo.toml | 24 ++ .../binary_only/libafl_qemu_trace/README.md | 7 + .../libafl_qemu_trace/src/env_config.rs | 179 ++++++++++ .../binary_only/libafl_qemu_trace/src/main.rs | 117 +++++++ .../binary_only/libafl_qemu_trace/src/tsl.rs | 310 ++++++++++++++++++ fuzzers/binary_only/qemu_coverage/Cargo.lock | 1 + fuzzers/binary_only/qemu_launcher/Cargo.lock | 1 + .../forkserver/forkserver_simple/Cargo.toml | 3 +- fuzzers/forkserver/libafl-fuzz/Cargo.lock | 1 + fuzzers/full_system/qemu_baremetal/Cargo.lock | 1 + .../full_system/qemu_linux_kernel/Cargo.lock | 1 + .../full_system/qemu_linux_process/Cargo.lock | 1 + libafl/src/observers/map/mod.rs | 134 +++++++- libafl/src/observers/mod.rs | 9 +- libafl_bolts/src/lib.rs | 34 +- libafl_qemu/libafl_qemu_build/src/bindings.rs | 2 + libafl_qemu/libafl_qemu_build/src/build.rs | 2 +- libafl_qemu/src/elf.rs | 22 ++ libafl_qemu/src/emu/hooks.rs | 5 +- libafl_qemu/src/emu/usermode.rs | 18 +- libafl_qemu/src/modules/drcov.rs | 5 +- libafl_qemu/src/modules/edges/full.rs | 4 +- libafl_qemu/src/modules/edges/helpers.rs | 11 +- libafl_qemu/src/modules/edges/mod.rs | 25 +- libafl_qemu/src/modules/utils/filters.rs | 3 + libafl_qemu/src/qemu/hooks.rs | 44 ++- libafl_qemu/src/qemu/usermode.rs | 65 +++- libafl_targets/Cargo.toml | 1 + libafl_targets/build.rs | 11 + libafl_targets/src/cmps/mod.rs | 4 +- libafl_targets/src/forkserver.c | 106 ++---- libafl_targets/src/forkserver.h | 93 ++++++ libafl_targets/src/forkserver.rs | 202 +++++++++++- 35 files changed, 1377 insertions(+), 145 deletions(-) create mode 100644 fuzzers/binary_only/libafl_qemu_trace/Cargo.toml create mode 100644 fuzzers/binary_only/libafl_qemu_trace/README.md create mode 100644 fuzzers/binary_only/libafl_qemu_trace/src/env_config.rs create mode 100644 fuzzers/binary_only/libafl_qemu_trace/src/main.rs create mode 100644 fuzzers/binary_only/libafl_qemu_trace/src/tsl.rs create mode 100644 libafl_targets/src/forkserver.h diff --git a/fuzzers/binary_only/fuzzbench_fork_qemu/Cargo.lock b/fuzzers/binary_only/fuzzbench_fork_qemu/Cargo.lock index 5c3ffce4baf..ab265a3369d 100644 --- a/fuzzers/binary_only/fuzzbench_fork_qemu/Cargo.lock +++ b/fuzzers/binary_only/fuzzbench_fork_qemu/Cargo.lock @@ -772,12 +772,35 @@ dependencies = [ "syn", ] +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_home" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" +[[package]] +name = "env_logger" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -864,6 +887,7 @@ name = "fuzzbench_fork_qemu" version = "0.15.2" dependencies = [ "clap", + "env_logger", "libafl", "libafl_bolts", "libafl_qemu", @@ -1078,6 +1102,30 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "jobserver" version = "0.1.32" @@ -1603,6 +1651,21 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "postcard" version = "1.1.1" diff --git a/fuzzers/binary_only/fuzzbench_fork_qemu/Justfile b/fuzzers/binary_only/fuzzbench_fork_qemu/Justfile index 5fa1ef611bc..688512fbee2 100644 --- a/fuzzers/binary_only/fuzzbench_fork_qemu/Justfile +++ b/fuzzers/binary_only/fuzzbench_fork_qemu/Justfile @@ -17,14 +17,11 @@ build: [unix] run: build harness - cargo run \ - --profile {{ PROFILE }} \ - ./{{ FUZZER_NAME }} \ + {{ FUZZER }} \ + {{ BUILD_DIR }}/harness \ -- \ - --libafl-in ../../inprocess/libfuzzer_libpng/corpus \ - --libafl-out ./out \ - ./{{ FUZZER_NAME }} - + --libafl-in ./corpus \ + --libafl-out ./out [unix] test: build harness @@ -41,4 +38,4 @@ test: build harness [unix] clean: - cargo clean \ No newline at end of file + cargo clean diff --git a/fuzzers/binary_only/libafl_qemu_trace/Cargo.toml b/fuzzers/binary_only/libafl_qemu_trace/Cargo.toml new file mode 100644 index 00000000000..847d88be3c9 --- /dev/null +++ b/fuzzers/binary_only/libafl_qemu_trace/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "libafl-qemu-trace" +authors = [ + "Romain Malmain ", + "Andrea Fioraldi ", +] +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 diff --git a/fuzzers/binary_only/libafl_qemu_trace/README.md b/fuzzers/binary_only/libafl_qemu_trace/README.md new file mode 100644 index 00000000000..06bf641106f --- /dev/null +++ b/fuzzers/binary_only/libafl_qemu_trace/README.md @@ -0,0 +1,7 @@ +# LibAFL QEMU Trace + +An adaptation of AFL++'s [qemuafl](https://github.com/AFLplusplus/qemuafl/). + +## Supported environment variables + +- \ No newline at end of file diff --git a/fuzzers/binary_only/libafl_qemu_trace/src/env_config.rs b/fuzzers/binary_only/libafl_qemu_trace/src/env_config.rs new file mode 100644 index 00000000000..4a9ab7f04a0 --- /dev/null +++ b/fuzzers/binary_only/libafl_qemu_trace/src/env_config.rs @@ -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> = 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 { + match s.as_str() { + "0" => Ok(false), + "1" => Ok(true), + _ => Err(format!("Couldn't convert {s} into a boolean.")), + } +} + +fn env_ranges(s: String) -> Result>, String> +where + T: FromStr, + ::Err: fmt::Display, +{ + let ranges_to_parse = s.split(","); + + let mut ranges: Vec> = 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::() + .map_err(|e| format!("Couldn't parse range start address: {e}"))?; + + let end_addr = range_bounds + .next() + .unwrap() + .parse::() + .map_err(|e| format!("Couldn't parse range end address: {e}"))?; + + ranges.push(start_addr..end_addr); + } + + Ok(ranges) +} + +fn env_val(s: String) -> Result +where + T: FromStr, + ::Err: fmt::Display, +{ + s.parse::().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::); + } +} + +macro_rules! def_env_addr { + ( + $(#[$meta:meta])* + $env_name:ident + ) => { + $(#[$meta])* + def_env!($env_name, GuestAddr, env_val::); + } +} + +macro_rules! def_env_addr_ranges { + ( + $(#[$meta:meta])* + $env_name:ident + ) => { + $(#[$meta])* + def_env!($env_name, Vec>, env_ranges::); + } +} + +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); diff --git a/fuzzers/binary_only/libafl_qemu_trace/src/main.rs b/fuzzers/binary_only/libafl_qemu_trace/src/main.rs new file mode 100644 index 00000000000..b3888ff279e --- /dev/null +++ b/fuzzers/binary_only/libafl_qemu_trace/src/main.rs @@ -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 = 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::::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::, _>::empty() + .qemu_parameters(&args) + .modules(modules) + .build() + .unwrap(); + + let mut state = NopState::::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!(); +} diff --git a/fuzzers/binary_only/libafl_qemu_trace/src/tsl.rs b/fuzzers/binary_only/libafl_qemu_trace/src/tsl.rs new file mode 100644 index 00000000000..c8447d933a6 --- /dev/null +++ b/fuzzers/binary_only/libafl_qemu_trace/src/tsl.rs @@ -0,0 +1,310 @@ +//! AFL++ style TSL caching + +use std::{ + fmt::Debug, + fs::File, + io::{Read, Write}, + marker::PhantomData, + os::{ + fd::{AsRawFd, FromRawFd, RawFd}, + raw::c_int, + }, + process, +}; + +use libafl_bolts::{any_as_bytes, any_as_bytes_mut, tuples::MatchFirstType}; +use libafl_qemu::{ + BlockExecHook, BlockGenHook, BlockPostGenHook, CPU, EmulatorModules, GuestAddr, Qemu, + modules::{ + EmulatorModule, EmulatorModuleTuple, + utils::filters::{HasAddressFilter, NopAddressFilter}, + }, + sys::{GuestUsize, GuestVirtAddr, TranslationBlock}, +}; +use libafl_targets::{FORKSRV_FD, ForkserverHook}; +use nix::{ + libc, + sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction}, + unistd::{Pid, close, dup2, pipe}, +}; + +/// Forkserver tsl file descriptor. +/// Save as aflpp's tsl fd. +pub const TSL_FD: i32 = (FORKSRV_FD as i32) - 1; + +#[derive(Clone, Debug, Default)] +struct TslTb { + pc: GuestVirtAddr, + cs_base: u64, + flags: u32, + cflags: u32, +} + +#[derive(Clone, Debug, Default)] +struct TslChain { + last_tb: TslTb, + tb_exit: i32, +} + +#[derive(Clone, Debug, Default)] +struct TslRequest { + tb: TslTb, + chain: Option, +} + +/// Forkserver hook responsible for sending (in child) and receiving (in parent) tsl requests. +#[derive(Debug)] +pub struct TSLForkserverHook { + tsl_read_file: Option, +} + +/// Emulator module hooking qemu generated blocks, and transfering the requests to the forkserver hook. +/// Must be created through [`TSLForkserverHook`]. +#[derive(Debug)] +pub struct TSLModule { + addr_filter: NopAddressFilter, + tsl_write_file: Option, + phantom: PhantomData<(I, S)>, +} + +extern "C" fn close_tsl_fd() {} + +impl TSLModule { + /// Create a new [`TSLModule`] from a [`TSLForkserverHook`]. + pub fn new() -> Self { + Self { + addr_filter: NopAddressFilter, + phantom: PhantomData, + tsl_write_file: None, + } + } +} + +impl TSLForkserverHook { + pub fn new() -> Self { + TSLForkserverHook { + tsl_read_file: None, + } + } + + /// Collect tsl requests, and perform them on reception. + pub fn wait_tsl(&mut self, qemu: Qemu, cpu: CPU) { + let mut tsl_request = TslRequest::default(); + + loop { + unsafe { + match self + .tsl_read_file + .as_mut() + .unwrap_unchecked() + .read(any_as_bytes_mut(&mut tsl_request)) + { + Ok(nbytes) => { + // Read is correct, handle the request. + if nbytes == 0 { + // child process is dead, we can end the loop + break; + } + + assert_eq!(nbytes, size_of_val(&tsl_request)) + } + Err(read_err) => match read_err.kind() { + std::io::ErrorKind::UnexpectedEof | std::io::ErrorKind::Interrupted => { + break; + } + _ => { + panic!("Unexpected error while reading tsl request: {read_err:?}") + } + }, + } + } + + let mut valid_pc = true; + + // check if the tb exist + let mut tb = unsafe { + qemu.tb_lookup( + cpu, + tsl_request.tb.pc, + tsl_request.tb.cs_base, + tsl_request.tb.flags, + tsl_request.tb.cflags, + ) + }; + + if tb.is_null() { + if qemu.is_valid_addr(tsl_request.tb.pc) { + // if it does not exist and the address is valid, generate the cached tb + + tb = unsafe { + qemu.tb_gen_code( + cpu, + tsl_request.tb.pc, + tsl_request.tb.cs_base, + tsl_request.tb.flags, + tsl_request.tb.cflags as i32, + ) + }; + } else { + valid_pc = false; + } + } + + debug_assert!(!tb.is_null()); + + if valid_pc { + // if pc was valid, try to chain blocks when possible + if let Some(chain) = &tsl_request.chain { + let last_tb = unsafe { + qemu.tb_lookup( + cpu, + chain.last_tb.pc, + chain.last_tb.cs_base, + chain.last_tb.flags, + chain.last_tb.cflags, + ) + }; + + unsafe { + if !last_tb.is_null() + && ((*last_tb).jmp_reset_offset[chain.tb_exit as usize] + != libafl_qemu_sys::TB_JMP_OFFSET_INVALID as u16) + { + qemu.tb_add_jump(last_tb, chain.tb_exit, tb); + } + } + } + } + } + } +} + +extern "C" fn parent_sigchld_handler(_: c_int) {} + +impl ForkserverHook for TSLForkserverHook { + fn pre_fork(&mut self) { + // setup the tsl communication channel + let (read_fd, write_fd) = pipe().unwrap(); + + // dup write fd to a known value. + let _tsl_fd = dup2(write_fd.as_raw_fd(), TSL_FD as RawFd).unwrap(); + + // set read fd in forkserver hook. + self.tsl_read_file = Some(File::from(read_fd)); + } + + fn post_parent_fork(&mut self, _pid: Pid) { + // drop write fd in child + close(TSL_FD).unwrap(); + } + + fn post_child_fork(&mut self) { + // drop read fd in child + self.tsl_read_file.take(); + } + + fn pre_parent_wait(&mut self) { + // at this point, QEMU should be correctly initialized, + // and the current CPU should be the one running the target. + let qemu = Qemu::get().unwrap(); + let cpu = qemu.current_cpu().unwrap(); + + self.wait_tsl(qemu, cpu); + } +} + +fn tsl_block_post_gen_hook( + _qemu: Qemu, + emulator_modules: &mut EmulatorModules, + _state: Option<&mut S>, + pc: GuestAddr, + _block_length: GuestUsize, + tb: *const TranslationBlock, + last_tb: *const TranslationBlock, + tb_exit: i32, +) where + ET: MatchFirstType + Unpin, + I: Unpin + 'static, + S: Unpin + 'static, +{ + let module = + MatchFirstType::match_first_type_mut::>(emulator_modules.modules_mut()) + .unwrap(); + + // Collect TB and last TB important data + let tsl_request = unsafe { + let tb = TslTb { + pc, + cs_base: (*tb).cs_base, + flags: (*tb).flags, + cflags: (*tb).cflags, + }; + + let chain = if !last_tb.is_null() { + Some(TslChain { + last_tb: TslTb { + pc: (*last_tb).pc, + cs_base: (*last_tb).cs_base, + flags: (*last_tb).flags, + // WARNING: this differs from original tsl implementation + // it is using tb cflags, and not last_tb cflags. I think this is a bug, + // so I use the other one there. + cflags: (*last_tb).cflags, + }, + tb_exit, + }) + } else { + None + }; + + TslRequest { tb, chain } + }; + + // request tsl to the forkserver + unsafe { + module + .tsl_write_file + .as_mut() + .unwrap_unchecked() + .write_all(any_as_bytes(&tsl_request)) + .unwrap(); + } +} + +impl EmulatorModule for TSLModule +where + I: Debug + Unpin + 'static, + S: Debug + Unpin + 'static, +{ + fn first_exec( + &mut self, + _qemu: Qemu, + emulator_modules: &mut EmulatorModules, + _state: &mut S, + ) where + ET: EmulatorModuleTuple, + { + unsafe { + self.tsl_write_file = Some(File::from_raw_fd(TSL_FD)); + libc::atexit(close_tsl_fd); + } + + emulator_modules.blocks( + BlockGenHook::Empty, + BlockPostGenHook::Function(tsl_block_post_gen_hook), + BlockExecHook::Empty, + ); + } +} + +impl HasAddressFilter for TSLModule { + type AddressFilter = NopAddressFilter; + + fn address_filter(&self) -> &Self::AddressFilter { + &self.addr_filter + } + + fn address_filter_mut(&mut self) -> &mut Self::AddressFilter { + &mut self.addr_filter + } +} diff --git a/fuzzers/binary_only/qemu_coverage/Cargo.lock b/fuzzers/binary_only/qemu_coverage/Cargo.lock index e0ea59d968e..6dc9f377f3e 100644 --- a/fuzzers/binary_only/qemu_coverage/Cargo.lock +++ b/fuzzers/binary_only/qemu_coverage/Cargo.lock @@ -1592,6 +1592,7 @@ dependencies = [ "libafl_bolts", "libc", "log", + "nix", "once_cell", "rangemap", "rustversion", diff --git a/fuzzers/binary_only/qemu_launcher/Cargo.lock b/fuzzers/binary_only/qemu_launcher/Cargo.lock index c16c3722c2d..bbddd7ba5d1 100644 --- a/fuzzers/binary_only/qemu_launcher/Cargo.lock +++ b/fuzzers/binary_only/qemu_launcher/Cargo.lock @@ -1592,6 +1592,7 @@ dependencies = [ "libafl_bolts", "libc", "log", + "nix", "once_cell", "rangemap", "rustversion", diff --git a/fuzzers/forkserver/forkserver_simple/Cargo.toml b/fuzzers/forkserver/forkserver_simple/Cargo.toml index 926725324df..6b3f75a35b6 100644 --- a/fuzzers/forkserver/forkserver_simple/Cargo.toml +++ b/fuzzers/forkserver/forkserver_simple/Cargo.toml @@ -11,9 +11,10 @@ panic = "abort" [profile.release] panic = "abort" -lto = true +lto = "fat" codegen-units = 1 opt-level = 3 +debug = true [dependencies] clap = { version = "4.5.18", features = ["derive"] } diff --git a/fuzzers/forkserver/libafl-fuzz/Cargo.lock b/fuzzers/forkserver/libafl-fuzz/Cargo.lock index f683efd9d51..7fc5afb46ab 100644 --- a/fuzzers/forkserver/libafl-fuzz/Cargo.lock +++ b/fuzzers/forkserver/libafl-fuzz/Cargo.lock @@ -1004,6 +1004,7 @@ dependencies = [ "libafl_bolts", "libc", "log", + "nix 0.29.0", "once_cell", "rangemap", "rustversion", diff --git a/fuzzers/full_system/qemu_baremetal/Cargo.lock b/fuzzers/full_system/qemu_baremetal/Cargo.lock index d99c7d46027..0346a60a8db 100644 --- a/fuzzers/full_system/qemu_baremetal/Cargo.lock +++ b/fuzzers/full_system/qemu_baremetal/Cargo.lock @@ -1346,6 +1346,7 @@ dependencies = [ "libafl_bolts", "libc", "log", + "nix", "once_cell", "rangemap", "rustversion", diff --git a/fuzzers/full_system/qemu_linux_kernel/Cargo.lock b/fuzzers/full_system/qemu_linux_kernel/Cargo.lock index c92e0b4ed87..ccfad2a8ad1 100644 --- a/fuzzers/full_system/qemu_linux_kernel/Cargo.lock +++ b/fuzzers/full_system/qemu_linux_kernel/Cargo.lock @@ -1346,6 +1346,7 @@ dependencies = [ "libafl_bolts", "libc", "log", + "nix", "once_cell", "rangemap", "rustversion", diff --git a/fuzzers/full_system/qemu_linux_process/Cargo.lock b/fuzzers/full_system/qemu_linux_process/Cargo.lock index d7d1cada585..380c0cd4996 100644 --- a/fuzzers/full_system/qemu_linux_process/Cargo.lock +++ b/fuzzers/full_system/qemu_linux_process/Cargo.lock @@ -1346,6 +1346,7 @@ dependencies = [ "libafl_bolts", "libc", "log", + "nix", "once_cell", "rangemap", "rustversion", diff --git a/libafl/src/observers/map/mod.rs b/libafl/src/observers/map/mod.rs index 89ea01ea9d3..d4b9aa9da7a 100644 --- a/libafl/src/observers/map/mod.rs +++ b/libafl/src/observers/map/mod.rs @@ -7,7 +7,9 @@ use core::{ ops::{Deref, DerefMut}, }; -use libafl_bolts::{AsSlice, AsSliceMut, HasLen, Named, Truncate, ownedref::OwnedMutSlice}; +use libafl_bolts::{ + AsSlice, AsSliceMut, HasLen, Named, Truncate, hasher_std, ownedref::OwnedMutSlice, +}; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use crate::{ @@ -403,6 +405,121 @@ pub trait VarLenMapObserver: MapObserver { fn size_mut(&mut self) -> &mut usize; } +/// Implementors guarantee the size of the map is constant at any point in time and equals N. +pub trait HasVarLenMap { + /// A map entry + type Entry: PartialEq + Copy; + + /// A mutable slice reference to the map. + /// The length of the map gives the maximum allocatable size. + fn map_ptr(&self) -> *const Self::Entry; + + /// A slice reference to the map. + /// The length of the map gives the maximum allocatable size. + fn map_ptr_mut(&mut self) -> *mut Self::Entry; + + /// A reference to the size of the map. + fn size_ptr(&self) -> *const usize; + + /// A mutable reference to the size of the map. + fn size_ptr_mut(&mut self) -> *mut usize; + + /// The max size of the map + fn max_size(&self) -> usize; +} + +/// Implementors guarantee the size of the map is constant at any point in time and equals N. +pub trait HasConstLenMap { + /// A map entry + type Entry: PartialEq + Copy; + + /// The size of the map + const LENGTH: usize = N; + + /// A const ptr to the map + fn map_ptr(&self) -> *const Self::Entry; + + /// A mutable ptr to the map + fn map_ptr_mut(&mut self) -> *mut Self::Entry; +} + +/// A wrapper around a const map +#[derive(Debug)] +pub struct ConstBytesMap { + map_ptr: *mut u8, +} + +/// A wrapper around a const map +#[derive(Debug)] +pub struct VarLenBytesMap { + map_ptr: *mut u8, + size_ptr: *mut usize, + max_size: usize, +} + +impl ConstBytesMap { + /// Create a new const bytes map + /// + /// # Safety + /// + /// The map must be allocated at this point. + pub unsafe fn new(map_ptr: *mut u8) -> Self { + Self { map_ptr } + } +} + +impl VarLenBytesMap { + /// Create a new variable length bytes map + /// + /// # Safety + /// + /// The map and the size must be allocated at this point. + /// invariant: the map should always have size elements + pub unsafe fn new(map_ptr: *mut u8, size_ptr: *mut usize, max_size: usize) -> Self { + Self { + map_ptr, + size_ptr, + max_size, + } + } +} + +impl HasVarLenMap for VarLenBytesMap { + type Entry = u8; + + fn map_ptr(&self) -> *const Self::Entry { + self.map_ptr as *const Self::Entry + } + + fn map_ptr_mut(&mut self) -> *mut Self::Entry { + self.map_ptr + } + + fn size_ptr(&self) -> *const usize { + self.size_ptr as *const usize + } + + fn size_ptr_mut(&mut self) -> *mut usize { + self.size_ptr + } + + fn max_size(&self) -> usize { + self.max_size + } +} + +impl HasConstLenMap for ConstBytesMap { + type Entry = u8; + + fn map_ptr(&self) -> *const Self::Entry { + self.map_ptr as *const u8 + } + + fn map_ptr_mut(&mut self) -> *mut Self::Entry { + self.map_ptr + } +} + /// Implementors guarantee the size of the map is constant at any point in time and equals N. pub trait ConstLenMapObserver: MapObserver { /// The size of the map @@ -447,11 +564,26 @@ pub struct StdMapObserver<'a, T, const DIFFERENTIAL: bool> { impl Observer for StdMapObserver<'_, T, false> where Self: MapObserver, + T: Hash, { #[inline] fn pre_exec(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> { self.reset_map() } + + fn post_exec( + &mut self, + _state: &mut S, + _input: &I, + _exit_kind: &ExitKind, + ) -> Result<(), Error> { + let map = self.map.as_ref(); + + let mut hasher = hasher_std(); + map.hash(&mut hasher); + // println!("map hash: {:#x}", hasher.finish()); + Ok(()) + } } impl Observer for StdMapObserver<'_, T, true> {} diff --git a/libafl/src/observers/mod.rs b/libafl/src/observers/mod.rs index 10bc9fee730..765ce269b76 100644 --- a/libafl/src/observers/mod.rs +++ b/libafl/src/observers/mod.rs @@ -195,25 +195,24 @@ pub trait ObserverWithHashField { /// cleanup in `Observer::post_exec`. For individual executions, use /// `DifferentialObserver::{pre,post}_observe_{first,second}` as necessary for first and second, /// respectively. -#[expect(unused_variables)] pub trait DifferentialObserver: Observer { /// Perform an operation with the first set of observers before they are `pre_exec`'d. - fn pre_observe_first(&mut self, observers: &mut OTA) -> Result<(), Error> { + fn pre_observe_first(&mut self, _observers: &mut OTA) -> Result<(), Error> { Ok(()) } /// Perform an operation with the first set of observers after they are `post_exec`'d. - fn post_observe_first(&mut self, observers: &mut OTA) -> Result<(), Error> { + fn post_observe_first(&mut self, _observers: &mut OTA) -> Result<(), Error> { Ok(()) } /// Perform an operation with the second set of observers before they are `pre_exec`'d. - fn pre_observe_second(&mut self, observers: &mut OTB) -> Result<(), Error> { + fn pre_observe_second(&mut self, _observers: &mut OTB) -> Result<(), Error> { Ok(()) } /// Perform an operation with the second set of observers after they are `post_exec`'d. - fn post_observe_second(&mut self, observers: &mut OTB) -> Result<(), Error> { + fn post_observe_second(&mut self, _observers: &mut OTB) -> Result<(), Error> { Ok(()) } } diff --git a/libafl_bolts/src/lib.rs b/libafl_bolts/src/lib.rs index b972d850484..cd0cc755f38 100644 --- a/libafl_bolts/src/lib.rs +++ b/libafl_bolts/src/lib.rs @@ -183,7 +183,7 @@ use core::{ fmt::{self, Display}, num::{ParseIntError, TryFromIntError}, ops::{Deref, DerefMut}, - time, + slice, time, }; #[cfg(feature = "std")] use std::{env::VarError, io}; @@ -831,7 +831,7 @@ where { type Item = S::Entry; type Ref = &'it Self::Item; - type IntoIter = core::slice::Iter<'it, Self::Item>; + type IntoIter = slice::Iter<'it, Self::Item>; fn as_iter(&'it self) -> Self::IntoIter { self.as_slice().iter() @@ -855,7 +855,7 @@ where T: 'it, { type RefMut = &'it mut Self::Item; - type IntoIterMut = core::slice::IterMut<'it, Self::Item>; + type IntoIterMut = slice::IterMut<'it, Self::Item>; fn as_iter_mut(&'it mut self) -> Self::IntoIterMut { self.as_slice_mut().iter_mut() @@ -1571,6 +1571,34 @@ where } } +/// Cast any sized object into a byte array. +pub fn any_as_bytes(any: &T) -> &[u8] { + unsafe { slice::from_raw_parts(any as *const T as *const u8, size_of::()) } +} + +/// Cast any sized object into a mutable byte array. +/// +/// # Safety +/// +/// It is obviously dangerous, since it becomes possible to corrupt the +/// input object. To use with care. +pub unsafe fn any_as_bytes_mut(any: &mut T) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(any as *mut T as *mut u8, size_of::()) } +} + +/// Cast back a byte array into a given struct type. +/// There is no check performed regarding the validity of the operation. +/// +/// # Safety +/// +/// Of course, it is highly unsafe in general, since it can create arbitrary +/// objects from unchecked memory. +/// +/// The safest way to use this function is in combination with [`any_as_bytes`]. +pub unsafe fn any_from_bytes(any_bytes: &[u8]) -> &T { + unsafe { &*(any_bytes.as_ptr() as *const T) } +} + #[cfg(test)] mod tests { diff --git a/libafl_qemu/libafl_qemu_build/src/bindings.rs b/libafl_qemu/libafl_qemu_build/src/bindings.rs index a326c8ad0c3..05aa2851eed 100644 --- a/libafl_qemu/libafl_qemu_build/src/bindings.rs +++ b/libafl_qemu/libafl_qemu_build/src/bindings.rs @@ -72,6 +72,7 @@ const WRAPPER_HEADER: &str = r#" #include "exec/cpu-all.h" #include "exec/exec-all.h" #include "exec/translate-all.h" +#include "exec/translation-block.h" #include "exec/log.h" #include "trace/trace-root.h" #include "qemu/accel.h" @@ -138,6 +139,7 @@ pub fn generate( .allowlist_var("mmap_next_start") .allowlist_var("guest_base") .allowlist_var("exec_path") + .allowlist_var("TB_JMP_OFFSET_INVALID") .allowlist_type("target_ulong") .allowlist_type("target_long") .allowlist_type("CPUState") diff --git a/libafl_qemu/libafl_qemu_build/src/build.rs b/libafl_qemu/libafl_qemu_build/src/build.rs index acd4bd83cc5..bea34294b7c 100644 --- a/libafl_qemu/libafl_qemu_build/src/build.rs +++ b/libafl_qemu/libafl_qemu_build/src/build.rs @@ -11,7 +11,7 @@ use crate::cargo_add_rpath; pub const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; pub const QEMU_DIRNAME: &str = "qemu-libafl-bridge"; -pub const QEMU_REVISION: &str = "2a676d9cd8c474b5c0db1d77d2769e56e2ed8524"; +pub const QEMU_REVISION: &str = "6dffcae1d6603aae5c6049d540400330167b73c5"; pub struct BuildResult { pub qemu_path: PathBuf, diff --git a/libafl_qemu/src/elf.rs b/libafl_qemu/src/elf.rs index fba390a0c68..e778c5fdf2f 100644 --- a/libafl_qemu/src/elf.rs +++ b/libafl_qemu/src/elf.rs @@ -6,6 +6,7 @@ use goblin::elf::{Elf, header::ET_DYN}; use libafl::Error; use libafl_qemu_sys::GuestAddr; +#[derive(Debug)] pub struct EasyElf<'a> { elf: Elf<'a>, } @@ -71,6 +72,27 @@ impl<'a> EasyElf<'a> { } } } + + if self.elf.is_lib { + for sym in &self.elf.dynsyms { + if let Some(sym_name) = self.elf.dynstrtab.get_at(sym.st_name) { + if sym_name == name { + return if sym.st_value == 0 { + None + } else { + #[cfg(cpu_target = "arm")] + // Required because of arm interworking addresses aka bit(0) for thumb mode + let addr = + (sym.st_value as GuestAddr + load_addr) & !(0x1 as GuestAddr); + #[cfg(not(cpu_target = "arm"))] + let addr = sym.st_value as GuestAddr + load_addr; + Some(addr) + }; + } + } + } + } + None } diff --git a/libafl_qemu/src/emu/hooks.rs b/libafl_qemu/src/emu/hooks.rs index 5d2eb85723d..225f84cb6b3 100644 --- a/libafl_qemu/src/emu/hooks.rs +++ b/libafl_qemu/src/emu/hooks.rs @@ -3,7 +3,7 @@ use std::{fmt::Debug, marker::PhantomData, mem::transmute, pin::Pin, ptr}; use libafl::{executors::ExitKind, observers::ObserversTuple}; -use libafl_qemu_sys::{CPUStatePtr, FatPtr, GuestAddr, GuestUsize, TCGTemp}; +use libafl_qemu_sys::{CPUStatePtr, FatPtr, GuestAddr, GuestUsize, TCGTemp, TranslationBlock}; #[cfg(feature = "usermode")] use crate::qemu::{ @@ -337,6 +337,9 @@ where &mut TcgHookState<1, BlockHookId>, pc: GuestAddr, block_length: GuestUsize, + tb: *const TranslationBlock, + last_tb: *const TranslationBlock, + tb_exit: i32, ) ); diff --git a/libafl_qemu/src/emu/usermode.rs b/libafl_qemu/src/emu/usermode.rs index b0d5d523f2f..c359ea1a347 100644 --- a/libafl_qemu/src/emu/usermode.rs +++ b/libafl_qemu/src/emu/usermode.rs @@ -1,7 +1,9 @@ use libafl_bolts::Error; use libafl_qemu_sys::{GuestAddr, MmapPerms, VerifyAccess}; -use crate::{Emulator, GuestMaps, NopSnapshotManager, TargetSignalHandling}; +use crate::{ + Emulator, GuestMaps, NopSnapshotManager, TargetSignalHandling, modules::EmulatorModuleTuple, +}; pub type StdSnapshotManager = NopSnapshotManager; @@ -98,3 +100,17 @@ impl Emulator { } } } + +impl Emulator +where + ET: EmulatorModuleTuple, + S: Unpin, +{ + pub unsafe fn run_target_crash_hooks_on_dying_signal(&self) { + unsafe { + libafl_qemu_sys::libafl_set_on_signal_handler(Some( + super::hooks::run_target_crash_hooks::, + )); + } + } +} diff --git a/libafl_qemu/src/modules/drcov.rs b/libafl_qemu/src/modules/drcov.rs index 9b48428f83d..a65dc789bcf 100644 --- a/libafl_qemu/src/modules/drcov.rs +++ b/libafl_qemu/src/modules/drcov.rs @@ -7,7 +7,7 @@ use std::{path::PathBuf, sync::Mutex}; use hashbrown::{HashMap, hash_map::Entry}; use libafl::{HasMetadata, executors::ExitKind, observers::ObserversTuple}; -use libafl_qemu_sys::{GuestAddr, GuestUsize}; +use libafl_qemu_sys::{GuestAddr, GuestUsize, TranslationBlock}; use libafl_targets::drcov::{DrCovBasicBlock, DrCovWriter}; use rangemap::RangeMap; use serde::{Deserialize, Serialize}; @@ -173,6 +173,9 @@ pub fn gen_block_lengths( _state: Option<&mut S>, pc: GuestAddr, block_length: GuestUsize, + _tb: *const TranslationBlock, + _last_tb: *const TranslationBlock, + _tb_exit: i32, ) where ET: EmulatorModuleTuple, F: AddressFilter, diff --git a/libafl_qemu/src/modules/edges/full.rs b/libafl_qemu/src/modules/edges/full.rs index 548fe6055e7..5389a4ef772 100644 --- a/libafl_qemu/src/modules/edges/full.rs +++ b/libafl_qemu/src/modules/edges/full.rs @@ -2,7 +2,7 @@ use libafl::HasMetadata; use super::{ EdgeCoverageVariant, - helpers::{gen_unique_edge_ids, trace_edge_hitcount, trace_edge_single}, + helpers::{gen_unique_edge_ids, trace_edge_hitcount_ptr, trace_edge_single}, }; use crate::{ EmulatorModules, Hook, @@ -80,7 +80,7 @@ impl { emulator_modules.edges( Hook::Function(gen_unique_edge_ids::), - Hook::Raw(trace_edge_hitcount), + Hook::Raw(trace_edge_hitcount_ptr), ); } diff --git a/libafl_qemu/src/modules/edges/helpers.rs b/libafl_qemu/src/modules/edges/helpers.rs index 9a92d16a171..eaf7b302283 100644 --- a/libafl_qemu/src/modules/edges/helpers.rs +++ b/libafl_qemu/src/modules/edges/helpers.rs @@ -7,8 +7,8 @@ use libafl_qemu_sys::GuestAddr; use serde::{Deserialize, Serialize}; /// Tracers, responsible for propagating an ID in a map. pub use tracers::{ - trace_block_transition_hitcount, trace_block_transition_single, trace_edge_hitcount, - trace_edge_hitcount_ptr, trace_edge_single, trace_edge_single_ptr, + trace_block_transition_hitcount, trace_block_transition_single, trace_edge_hitcount_ptr, + trace_edge_single, trace_edge_single_ptr, }; // Constants used for variable-length maps @@ -108,8 +108,10 @@ mod generators { { unsafe { assert!(LIBAFL_QEMU_EDGES_MAP_MASK_MAX > 0); - let edges_map_size_ptr = &raw const LIBAFL_QEMU_EDGES_MAP_SIZE_PTR; - assert_ne!(*edges_map_size_ptr, ptr::null_mut()); + if !IS_CONST_MAP { + let edges_map_size_ptr = LIBAFL_QEMU_EDGES_MAP_SIZE_PTR; + assert_ne!(edges_map_size_ptr, ptr::null_mut()); + } } #[cfg(feature = "usermode")] @@ -301,6 +303,7 @@ mod tracers { /// /// - @id should be the one generated by a gen_* function from this module. /// - Calling this concurrently for the same id is racey and may lose updates. + #[expect(dead_code)] pub unsafe extern "C" fn trace_edge_hitcount(_: *const (), id: u64) { unsafe { EDGES_MAP[id as usize] = EDGES_MAP[id as usize].wrapping_add(1); diff --git a/libafl_qemu/src/modules/edges/mod.rs b/libafl_qemu/src/modules/edges/mod.rs index b7f571f5c00..8df8f387870 100644 --- a/libafl_qemu/src/modules/edges/mod.rs +++ b/libafl_qemu/src/modules/edges/mod.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use libafl::{HasMetadata, observers::VarLenMapObserver}; +use libafl::HasMetadata; use libafl_bolts::Error; use libafl_qemu_sys::GuestAddr; #[cfg(feature = "systemmode")] @@ -32,7 +32,7 @@ pub mod child; pub use child::{ EdgeCoverageChildVariant, StdEdgeCoverageChildModule, StdEdgeCoverageChildModuleBuilder, }; -use libafl::observers::ConstLenMapObserver; +use libafl::observers::{HasConstLenMap, HasVarLenMap}; use super::utils::filters::HasAddressFilter; #[cfg(feature = "systemmode")] @@ -166,16 +166,13 @@ impl( - self, - map_observer: &mut O, - ) -> EdgeCoverageModuleBuilder + pub fn map(self, map: &mut M) -> EdgeCoverageModuleBuilder where - O: VarLenMapObserver, + M: HasVarLenMap, { - let map_ptr = map_observer.map_slice_mut().as_mut_ptr() as *mut u8; - let map_max_size = map_observer.map_slice_mut().len(); - let size_ptr = map_observer.as_mut().size_mut() as *mut usize; + let map_ptr = map.map_ptr_mut(); + let map_max_size = map.max_size(); + let size_ptr = map.size_ptr_mut(); unsafe { LIBAFL_QEMU_EDGES_MAP_PTR = map_ptr; @@ -194,14 +191,14 @@ impl( + pub fn const_map( self, - map_observer: &mut O, + map: &mut M, ) -> EdgeCoverageModuleBuilder where - O: ConstLenMapObserver, + M: HasConstLenMap, { - let map_ptr = map_observer.map_slice_mut().as_mut_ptr() as *mut u8; + let map_ptr = map.map_ptr_mut(); unsafe { LIBAFL_QEMU_EDGES_MAP_PTR = map_ptr; diff --git a/libafl_qemu/src/modules/utils/filters.rs b/libafl_qemu/src/modules/utils/filters.rs index 76cce54b498..a8c2c512f20 100644 --- a/libafl_qemu/src/modules/utils/filters.rs +++ b/libafl_qemu/src/modules/utils/filters.rs @@ -86,12 +86,15 @@ pub trait HasAddressFilter { type AddressFilter: AddressFilter; fn address_filter(&self) -> &Self::AddressFilter; + fn address_filter_mut(&mut self) -> &mut Self::AddressFilter; + fn update_address_filter(&mut self, qemu: Qemu, filter: Self::AddressFilter) { *self.address_filter_mut() = filter; // Necessary because some hooks filter during TB generation. qemu.flush_jit(); } + fn allow_address_range(&mut self, address_range: &Range) { self.address_filter_mut().register(address_range); } diff --git a/libafl_qemu/src/qemu/hooks.rs b/libafl_qemu/src/qemu/hooks.rs index 405a65a06dd..811fcf3da39 100644 --- a/libafl_qemu/src/qemu/hooks.rs +++ b/libafl_qemu/src/qemu/hooks.rs @@ -7,7 +7,9 @@ use core::{ffi::c_void, fmt::Debug, mem::transmute, ptr}; use libafl::executors::hooks::inprocess::inprocess_get_state; -use libafl_qemu_sys::{CPUArchStatePtr, CPUStatePtr, FatPtr, GuestAddr, GuestUsize}; +use libafl_qemu_sys::{ + CPUArchStatePtr, CPUStatePtr, FatPtr, GuestAddr, GuestUsize, TranslationBlock, +}; #[cfg(feature = "python")] use pyo3::{FromPyObject, pyclass, pymethods}; @@ -676,6 +678,9 @@ create_hook_types!( Option<&mut S>, pc: GuestAddr, block_length: GuestUsize, + tb: *const TranslationBlock, + last_tb: *const TranslationBlock, + tb_exit: i32, ), Box< dyn for<'a> FnMut( @@ -684,9 +689,19 @@ create_hook_types!( Option<&mut S>, GuestAddr, GuestUsize, + *const TranslationBlock, + *const TranslationBlock, + i32, ), >, - unsafe extern "C" fn(libafl_qemu_opaque: *const (), pc: GuestAddr, block_length: GuestUsize) + unsafe extern "C" fn( + libafl_qemu_opaque: *const (), + pc: GuestAddr, + block_length: GuestUsize, + tb: *mut TranslationBlock, + last_tb: *mut TranslationBlock, + tb_exit: i32, + ) ); create_hook_types!( BlockExec, @@ -697,7 +712,7 @@ create_hook_types!( create_hook_id!(Block, libafl_qemu_remove_block_hook, true); create_gen_wrapper!(block, (addr: GuestAddr), u64, 1, BlockHookId); -create_post_gen_wrapper!(block, (addr: GuestAddr, len: GuestUsize), 1, BlockHookId); +create_post_gen_wrapper!(block, (addr: GuestAddr, len: GuestUsize, tb: *const TranslationBlock, last_tb: *const TranslationBlock, tb_exit: i32), 1, BlockHookId); create_exec_wrapper!(block, (id: u64), 0, 1, BlockHookId); // Read hook wrappers @@ -1062,15 +1077,32 @@ impl QemuHooks { &self, data: T, generator: Option u64>, - post_gen: Option, + post_gen: Option< + unsafe extern "C" fn( + T, + GuestAddr, + GuestUsize, + *const TranslationBlock, + *const TranslationBlock, + i32, + ), + >, exec: Option, ) -> BlockHookId { unsafe { let data: u64 = data.into().0; let generator: Option u64> = transmute(generator); - let post_gen: Option = - transmute(post_gen); + let post_gen: Option< + unsafe extern "C" fn( + u64, + GuestAddr, + GuestUsize, + *mut TranslationBlock, + *mut TranslationBlock, + i32, + ), + > = transmute(post_gen); let exec: Option = transmute(exec); let num = libafl_qemu_sys::libafl_add_block_hook(generator, post_gen, exec, data); BlockHookId(num) diff --git a/libafl_qemu/src/qemu/usermode.rs b/libafl_qemu/src/qemu/usermode.rs index a41c563927d..f47c3ca3ec0 100644 --- a/libafl_qemu/src/qemu/usermode.rs +++ b/libafl_qemu/src/qemu/usermode.rs @@ -5,16 +5,16 @@ use std::{ use libafl_bolts::{Error, os::unix_signals::Signal}; use libafl_qemu_sys::{ - GuestAddr, GuestUsize, IntervalTreeNode, IntervalTreeRoot, MapInfo, MmapPerms, VerifyAccess, - exec_path, free_self_maps, guest_base, libafl_force_dfl, libafl_get_brk, - libafl_get_initial_brk, libafl_load_addr, libafl_maps_first, libafl_maps_next, libafl_qemu_run, - libafl_set_brk, mmap_next_start, pageflags_get_root, read_self_maps, + GuestAddr, GuestUsize, GuestVirtAddr, IntervalTreeNode, IntervalTreeRoot, MapInfo, MmapPerms, + TranslationBlock, VerifyAccess, exec_path, free_self_maps, guest_base, libafl_force_dfl, + libafl_get_brk, libafl_get_initial_brk, libafl_load_addr, libafl_maps_first, libafl_maps_next, + libafl_qemu_run, libafl_set_brk, mmap_next_start, pageflags_get_root, read_self_maps, }; use libc::{c_int, c_uchar, siginfo_t, strlen}; #[cfg(feature = "python")] use pyo3::{IntoPyObject, Py, PyRef, PyRefMut, Python, pyclass, pymethods}; -use crate::{CPU, Qemu, qemu::QEMU_IS_RUNNING}; +use crate::{CPU, Qemu, elf::EasyElf, qemu::QEMU_IS_RUNNING}; /// Choose how QEMU target signals should be handled. /// It's main use is to describe how crashes and timeouts should be treated. @@ -264,6 +264,10 @@ impl Qemu { unsafe { (addr as usize - guest_base) as GuestAddr } } + pub fn is_valid_addr(&self, addr: GuestAddr) -> bool { + unsafe { libafl_qemu_sys::libafl_is_valid_addr(addr) } + } + #[must_use] pub fn access_ok(&self, kind: VerifyAccess, addr: GuestAddr, size: usize) -> bool { self.current_cpu() @@ -293,6 +297,24 @@ impl Qemu { } } + /// Resolve a symbol dynamically. + pub fn resolve_symbol(&self, symbol: &str) -> Option { + let mut elf_buf = Vec::new(); + + for map in self.mappings() { + if let Some(path) = map.path() { + elf_buf.clear(); + if let Ok(elf) = EasyElf::from_file(path, &mut elf_buf) { + if let Some(symbol_addr) = elf.resolve_symbol(symbol, map.start()) { + return Some(symbol_addr); + } + } + } + } + + None + } + #[must_use] pub fn load_addr(&self) -> GuestAddr { unsafe { libafl_load_addr() as GuestAddr } @@ -461,6 +483,39 @@ impl Qemu { }, } } + + pub unsafe fn tb_lookup( + &self, + cpu: CPU, + pc: GuestVirtAddr, + cs_base: u64, + flags: u32, + cflags: u32, + ) -> *mut TranslationBlock { + unsafe { libafl_qemu_sys::libafl_tb_lookup(cpu.ptr, pc, cs_base, flags, cflags) } + } + + pub unsafe fn tb_gen_code( + &self, + cpu: CPU, + pc: GuestVirtAddr, + cs_base: u64, + flags: u32, + cflags: i32, + ) -> *mut TranslationBlock { + unsafe { libafl_qemu_sys::libafl_tb_gen_code(cpu.ptr, pc, cs_base, flags, cflags) } + } + + pub unsafe fn tb_add_jump( + &self, + tb: *mut TranslationBlock, + n: i32, + tb_next: *mut TranslationBlock, + ) { + unsafe { + libafl_qemu_sys::libafl_tb_add_jump(tb, n, tb_next); + } + } } #[cfg(feature = "python")] diff --git a/libafl_targets/Cargo.toml b/libafl_targets/Cargo.toml index 6e1839fb9fe..be0acc5ebc8 100644 --- a/libafl_targets/Cargo.toml +++ b/libafl_targets/Cargo.toml @@ -78,6 +78,7 @@ hashbrown = { workspace = true, default-features = true } once_cell = "1.19.0" log = { workspace = true } rustversion = { workspace = true } +nix = { workspace = true } rangemap = { workspace = true } serde = { workspace = true, default-features = false, features = [ diff --git a/libafl_targets/build.rs b/libafl_targets/build.rs index 46ad61d818b..434e3fa1270 100644 --- a/libafl_targets/build.rs +++ b/libafl_targets/build.rs @@ -256,6 +256,7 @@ fn main() { { if target_family == "unix" { println!("cargo:rerun-if-changed=src/forkserver.c"); + println!("cargo:rerun-if-changed=src/forkserver.h"); let mut forkserver = cc::Build::new(); @@ -267,6 +268,16 @@ fn main() { forkserver .file(src_dir.join("forkserver.c")) .compile("forkserver"); + + let forkserver_bindgen = bindgen::builder() + .header("src/forkserver.h") + .generate_comments(true) + .generate() + .expect("Couldn't generate the forkserver bindings!"); + + forkserver_bindgen + .write_to_file(Path::new(&out_dir).join("forkserver_bindings.rs")) + .expect("Couldn't write the forkserver bindings!"); } } diff --git a/libafl_targets/src/cmps/mod.rs b/libafl_targets/src/cmps/mod.rs index ba7d961a251..b855662f0f1 100644 --- a/libafl_targets/src/cmps/mod.rs +++ b/libafl_targets/src/cmps/mod.rs @@ -30,11 +30,11 @@ pub const CMPLOG_MAP_SIZE: usize = CMPLOG_MAP_W * CMPLOG_MAP_H; /// The size of a logged routine argument in bytes pub const CMPLOG_RTN_LEN: usize = 32; -/// The hight of a cmplog routine map +/// The height of a cmplog routine map pub const CMPLOG_MAP_RTN_H: usize = (CMPLOG_MAP_H * size_of::()) / size_of::(); -/// The height of extended rountine map +/// The height of extended routine map pub const CMPLOG_MAP_RTN_EXTENDED_H: usize = CMPLOG_MAP_H * size_of::() / size_of::(); diff --git a/libafl_targets/src/forkserver.c b/libafl_targets/src/forkserver.c index f5d16d4bc59..b2deab793b2 100644 --- a/libafl_targets/src/forkserver.c +++ b/libafl_targets/src/forkserver.c @@ -1,87 +1,23 @@ -#include "common.h" - -#include "android-ashmem.h" -#include -#include -#include -#include -#include -#include -#ifndef USEMMAP - #include -#else - #include - #include - #include -#endif -#include -#include - -#define write_error(s) \ - fprintf(stderr, "Error at %s:%d: %s\n", __FILE__, __LINE__, s) - -// AFL++ constants -#define FORKSRV_FD 198 -#define MAX_FILE (1024 * 1024) -#define SHMEM_FUZZ_HDR_SIZE 4 -#define SHM_ENV_VAR "__AFL_SHM_ID" -#define SHM_FUZZ_ENV_VAR "__AFL_SHM_FUZZ_ID" -#define DEFAULT_PERMISSION 0600 - -/* Reporting errors */ -#define FS_OPT_ERROR 0xf800008f -#define FS_OPT_GET_ERROR(x) ((x & 0x00ffff00) >> 8) -#define FS_OPT_SET_ERROR(x) ((x & 0x0000ffff) << 8) -#define FS_ERROR_MAP_SIZE 1 -#define FS_ERROR_MAP_ADDR 2 -#define FS_ERROR_SHM_OPEN 4 -#define FS_ERROR_SHMAT 8 -#define FS_ERROR_MMAP 16 -#define FS_ERROR_OLD_CMPLOG 32 -#define FS_ERROR_OLD_CMPLOG_QEMU 64 - -#define FS_NEW_VERSION_MAX 1 -#define FS_NEW_OPT_MAPSIZE 0x1 -#define FS_NEW_OPT_SHDMEM_FUZZ 0x2 -#define FS_NEW_OPT_AUTODICT 0x800 - -/* Reporting options */ -#define FS_OPT_ENABLED 0x80000001 -#define FS_OPT_MAPSIZE 0x40000000 -#define FS_OPT_SNAPSHOT 0x20000000 -#define FS_OPT_AUTODICT 0x10000000 -#define FS_OPT_SHDMEM_FUZZ 0x01000000 -#define FS_OPT_NEWCMPLOG 0x02000000 -#define FS_OPT_OLD_AFLPP_WORKAROUND 0x0f000000 -// FS_OPT_MAX_MAPSIZE is 8388608 = 0x800000 = 2^23 = 1 << 22 -#define FS_OPT_MAX_MAPSIZE ((0x00fffffeU >> 1) + 1) -#define FS_OPT_GET_MAPSIZE(x) (((x & 0x00fffffe) >> 1) + 1) -#define FS_OPT_SET_MAPSIZE(x) \ - (x <= 1 || x > FS_OPT_MAX_MAPSIZE ? 0 : ((x - 1) << 1)) +#include "forkserver.h" // Set by this macro // https://github.com/AFLplusplus/AFLplusplus/blob/stable/src/afl-cc.c#L993 int __afl_sharedmem_fuzzing __attribute__((weak)); -extern uint8_t *__afl_area_ptr; -extern size_t __afl_map_size; -extern uint8_t *__token_start; -extern uint8_t *__token_stop; - 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; static uint8_t is_persistent; -void __afl_set_persistent_mode(uint8_t mode) { +void __libafl_set_persistent_mode(uint8_t mode) { is_persistent = mode; } @@ -109,7 +45,7 @@ static void at_exit(int signal) { /* SHM fuzzing setup. */ -void __afl_map_shm(void) { +void __libafl_map_shm(void) { if (already_initialized_shm) return; already_initialized_shm = 1; @@ -160,7 +96,7 @@ void __afl_map_shm(void) { /* Write something into the bitmap so that even with low AFL_INST_RATIO, our parent doesn't give up on us. */ - __afl_area_ptr[0] = 1; + // __afl_area_ptr[0] = 1; } else { fprintf(stderr, "Error: variable for edge coverage shared memory is not set\n"); @@ -214,9 +150,11 @@ static void map_input_shared_memory() { } } -/* Fork server logic. */ +void __libafl_start_forkserver(void) { + __libafl_start_forkserver_with_hooks(NULL); +} -void __afl_start_forkserver(void) { +void __libafl_start_forkserver_with_hooks(struct libafl_forkserver_hook* hook) { if (already_initialized_forkserver) return; already_initialized_forkserver = 1; @@ -326,6 +264,10 @@ void __afl_start_forkserver(void) { if (!child_stopped) { /* Once woken up, create a clone of our process. */ + if (hook) { + hook->pre_fork_hook(hook->data); + } + child_pid = fork(); if (child_pid < 0) { write_error("fork"); @@ -342,7 +284,16 @@ void __afl_start_forkserver(void) { close(FORKSRV_FD); close(FORKSRV_FD + 1); + + if (hook) { + hook->post_child_fork_hook(hook->data); + } + return; + } else { + if (hook) { + hook->post_parent_fork_hook(hook->data, child_pid); + } } } else { @@ -360,6 +311,10 @@ void __afl_start_forkserver(void) { _exit(1); } + if (hook) { + hook->pre_parent_wait_hook(hook->data); + } + if (waitpid(child_pid, &status, is_persistent ? WUNTRACED : 0) < 0) { write_error("waitpid"); _exit(1); @@ -371,8 +326,15 @@ void __afl_start_forkserver(void) { if (WIFSTOPPED(status)) child_stopped = 1; + if (hook) { + hook->post_parent_wait_hook(hook->data, child_pid, status); + } + /* Relay wait status to pipe, then loop back. */ + // printf("child status: %d\n", WEXITSTATUS(status)); + printf("terminated by a signal? %d\n", WIFSIGNALED(status)); + if (write(FORKSRV_FD + 1, &status, 4) != 4) { write_error("writing to afl-fuzz"); _exit(1); diff --git a/libafl_targets/src/forkserver.h b/libafl_targets/src/forkserver.h new file mode 100644 index 00000000000..73bc6eb7fdf --- /dev/null +++ b/libafl_targets/src/forkserver.h @@ -0,0 +1,93 @@ +#ifndef FORKSERVER_H +#define FORKSERVER_H + +#include "common.h" + +#include "android-ashmem.h" +#include +#include +#include +#include +#include +#include +#ifndef USEMMAP + #include +#else + #include + #include + #include +#endif +#include +#include + +#define write_error(s) \ + fprintf(stderr, "Error at %s:%d: %s\n", __FILE__, __LINE__, s) + +// AFL++ constants +#define FORKSRV_FD 198 +#define MAX_FILE (1024 * 1024) +#define SHMEM_FUZZ_HDR_SIZE 4 +#define SHM_ENV_VAR "__AFL_SHM_ID" +#define SHM_FUZZ_ENV_VAR "__AFL_SHM_FUZZ_ID" +#define DEFAULT_PERMISSION 0600 + +/* Reporting errors */ +#define FS_OPT_ERROR 0xf800008f +#define FS_OPT_GET_ERROR(x) ((x & 0x00ffff00) >> 8) +#define FS_OPT_SET_ERROR(x) ((x & 0x0000ffff) << 8) +#define FS_ERROR_MAP_SIZE 1 +#define FS_ERROR_MAP_ADDR 2 +#define FS_ERROR_SHM_OPEN 4 +#define FS_ERROR_SHMAT 8 +#define FS_ERROR_MMAP 16 +#define FS_ERROR_OLD_CMPLOG 32 +#define FS_ERROR_OLD_CMPLOG_QEMU 64 + +#define FS_NEW_VERSION_MAX 1 +#define FS_NEW_OPT_MAPSIZE 0x1 +#define FS_NEW_OPT_SHDMEM_FUZZ 0x2 +#define FS_NEW_OPT_AUTODICT 0x800 + +/* Reporting options */ +#define FS_OPT_ENABLED 0x80000001 +#define FS_OPT_MAPSIZE 0x40000000 +#define FS_OPT_SNAPSHOT 0x20000000 +#define FS_OPT_AUTODICT 0x10000000 +#define FS_OPT_SHDMEM_FUZZ 0x01000000 +#define FS_OPT_NEWCMPLOG 0x02000000 +#define FS_OPT_OLD_AFLPP_WORKAROUND 0x0f000000 +// FS_OPT_MAX_MAPSIZE is 8388608 = 0x800000 = 2^23 = 1 << 22 +#define FS_OPT_MAX_MAPSIZE ((0x00fffffeU >> 1) + 1) +#define FS_OPT_GET_MAPSIZE(x) (((x & 0x00fffffe) >> 1) + 1) +#define FS_OPT_SET_MAPSIZE(x) \ + (x <= 1 || x > FS_OPT_MAX_MAPSIZE ? 0 : ((x - 1) << 1)) + +extern uint8_t *__afl_area_ptr; +extern size_t __afl_map_size; +extern uint8_t *__token_start; +extern uint8_t *__token_stop; +extern uint8_t *__afl_fuzz_ptr; +extern uint32_t *__afl_fuzz_len; + +struct libafl_forkserver_hook { + void* data; + void (*pre_fork_hook)(void* data); + void (*post_parent_fork_hook)(void* data, pid_t child_pid); + void (*post_child_fork_hook)(void* data); + void (*pre_parent_wait_hook)(void* data); + void (*post_parent_wait_hook)(void* data, pid_t wait_pid, int status); +}; + +/* Set persistent mode. */ +void __libafl_set_persistent_mode(uint8_t mode); + +/* SHM fuzzing setup. */ +void __libafl_map_shm(void); + +/* Fork server logic. */ +void __libafl_start_forkserver(void); + +/* Fork server logic, with hooks */ +void __libafl_start_forkserver_with_hooks(struct libafl_forkserver_hook* hook); + +#endif \ No newline at end of file diff --git a/libafl_targets/src/forkserver.rs b/libafl_targets/src/forkserver.rs index 79212bddd03..4f6f38d500d 100644 --- a/libafl_targets/src/forkserver.rs +++ b/libafl_targets/src/forkserver.rs @@ -1,26 +1,192 @@ //! Forkserver logic into targets -unsafe extern "C" { - /// Map a shared memory region for the edge coverage map. - fn __afl_map_shm(); - /// Start the forkserver. - fn __afl_start_forkserver(); +use core::ffi::c_int; +use std::{boxed::Box, os::raw::c_void, pin::Pin}; + +use libafl::Error; +use nix::{sys::wait::WaitStatus, unistd::Pid}; + +mod bindings { + #![expect(non_upper_case_globals)] + #![expect(non_camel_case_types)] + #![expect(non_snake_case)] + #![allow(improper_ctypes)] + #![allow(unused_mut)] + #![allow(unsafe_op_in_unsafe_fn)] + #![expect(unused)] + #![allow(unused_variables)] + #![allow(unused_qualifications)] + #![expect(clippy::all)] + #![expect(clippy::pedantic)] + #![expect(missing_docs)] + + include!(concat!(env!("OUT_DIR"), "/forkserver_bindings.rs")); } -/// Map a shared memory region for the edge coverage map. -/// -/// # Note +/// Default forkserver file descriptor. +pub use bindings::FORKSRV_FD; + +/// A Forkserver Hook. /// -/// The function's logic is written in C and this code is a wrapper. -pub fn map_shared_memory() { - unsafe { __afl_map_shm() } +/// It can be given while creating a [`Forkserver`] to perform additional actions +/// during the runtime of the forkserver. +pub trait ForkserverHook: Unpin { + /// Pre forkserver fork. + /// + /// There is no nothion of parent / child at this point. + /// Thus, the actions will be common to both. + fn pre_fork(&mut self) {} + + /// Post forkserver fork. + /// + /// This code is run by the *parent* just after it has been created. + fn post_parent_fork(&mut self, _pid: Pid) {} + + /// Post forkserver fork. + /// + /// This code is run by the *child* just after it has been created. + fn post_child_fork(&mut self) {} + + /// Pre parent wait. + /// + /// This hook is run just before the parent actually waits for the child to finish its execution. + fn pre_parent_wait(&mut self) {} + + /// Post parent wait + /// + /// This hook is run just after the parent has waited for the child to finish. + fn post_parent_wait(&mut self, _wait_status: &WaitStatus) {} } -/// Start the forkserver from this point. Any shared memory must be created before. -/// -/// # Note -/// -/// The forkserver logic is written in C and this code is a wrapper. -pub fn start_forkserver() { - unsafe { __afl_start_forkserver() } +/// Empty forkserver hook +#[derive(Debug)] +pub struct NopForkserverHook; + +impl ForkserverHook for NopForkserverHook {} + +/// Driver the native forkserver implementation. +#[derive(Debug)] +pub struct Forkserver { + hook: H, + shm_is_mapped: bool, +} + +impl Forkserver +where + H: ForkserverHook, +{ + extern "C" fn pre_fork_hook(fs: *mut c_void) { + let fs = fs as *mut Self; + + unsafe { + (*fs).hook.pre_fork(); + } + } + + extern "C" fn post_parent_fork_hook(fs: *mut c_void, child_pid: bindings::pid_t) { + let fs = fs as *mut Self; + + let child_pid = Pid::from_raw(child_pid); + + unsafe { + (*fs).hook.post_parent_fork(child_pid); + } + } + + extern "C" fn post_child_fork_hook(fs: *mut c_void) { + let fs = fs as *mut Self; + + unsafe { + (*fs).hook.post_child_fork(); + } + } + + extern "C" fn pre_parent_wait_hook(fs: *mut c_void) { + let fs = fs as *mut Self; + + unsafe { + (*fs).hook.pre_parent_wait(); + } + } + + extern "C" fn post_parent_wait_hook(fs: *mut c_void, wait_pid: bindings::pid_t, status: c_int) { + let fs = fs as *mut Self; + + let wait_pid = Pid::from_raw(wait_pid); + let status = WaitStatus::from_raw(wait_pid, status).unwrap(); + + unsafe { + (*fs).hook.post_parent_wait(&status); + } + } +} + +impl Forkserver +where + H: ForkserverHook, +{ + /// Create a new forkserver handler + pub fn new(hook: H) -> Pin> { + Box::pin(Self { + hook, + shm_is_mapped: false, + }) + } + + /// Get a raw pointer to the forkserver hook. + /// It is guaranteed to be pinned. + pub fn hook_ptr_mut(self: &mut Pin>) -> *mut H { + &mut self.as_mut().get_mut().hook as *mut H + } + + /// Map the shared map between the forkserver and the fuzzer. + /// It uses the env variable `__AFL_SHM_ID` to know what this ID to use for the SHM. + /// This will set `__afl_area_ptr` (pointer to the shared coverage map). + /// The size of the shared memory must be given by `__afl_map_size` prior to calling this function. + /// + /// # Safety + /// + /// `__afl_map_size` should be set prior to calling this function. + /// Since it's interacting with shared memory, it will have some side effects. + /// Check the implementation of `__afl_map_shm` to check if it is compatible with your use case. + pub unsafe fn map_shared_memory(self: &mut Pin>) { + unsafe { + bindings::__libafl_map_shm(); + } + + self.as_mut().get_mut().shm_is_mapped = true; + } + + /// Start the native forkserver. Any shared memory must be created before. + /// On return, we will be in the child process, and the parent process will wait for the process to end. + /// + /// # Safety + /// + /// This function has important impact on the underlying process. Make sure to understand what it is doing + /// prior to calling it. It is recommanded to read and understand the code of `__afl_start_forkserver`. + pub unsafe fn start_forkserver(self: &mut Pin>) -> Result<(), Error> { + if !self.shm_is_mapped { + return Err(Error::illegal_state( + "Shared memory should be mapped already. `map_shared_memory` should be called prior to starting the forkserver.", + )); + } + + let mut fs_hook = bindings::libafl_forkserver_hook { + data: self.as_mut().get_mut() as *mut Self as *mut c_void, + + pre_fork_hook: Some(Self::pre_fork_hook), + post_parent_fork_hook: Some(Self::post_parent_fork_hook), + post_child_fork_hook: Some(Self::post_child_fork_hook), + pre_parent_wait_hook: Some(Self::pre_parent_wait_hook), + post_parent_wait_hook: Some(Self::post_parent_wait_hook), + }; + + unsafe { + bindings::__libafl_start_forkserver_with_hooks( + &mut fs_hook as *mut bindings::libafl_forkserver_hook, + ); + } + + Ok(()) + } } From a179c95e763886f938f4d483f23aea35187ca9ad Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Thu, 10 Apr 2025 12:30:14 +0200 Subject: [PATCH 2/3] no test in ci for now --- .github/workflows/build_and_test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 5fe3b7cbd0f..9a10726696e 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -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 From c86b243c551049fca214c71ab3e0503447745cb0 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Thu, 10 Apr 2025 13:23:14 +0200 Subject: [PATCH 3/3] fixes --- libafl/src/observers/map/mod.rs | 29 +++++++--- libafl_bolts/src/lib.rs | 7 +-- libafl_qemu/libafl_qemu_build/src/bindings.rs | 1 + .../src/bindings/x86_64_stub_bindings.rs | 54 +++++++++++++++++-- .../runtime/libafl_qemu_stub_bindings.rs | 4 +- libafl_qemu/runtime/nyx_stub_bindings.rs | 4 +- libafl_qemu/src/modules/edges/mod.rs | 2 +- libafl_qemu/src/qemu/usermode.rs | 4 +- 8 files changed, 83 insertions(+), 22 deletions(-) diff --git a/libafl/src/observers/map/mod.rs b/libafl/src/observers/map/mod.rs index d4b9aa9da7a..5a21b92187f 100644 --- a/libafl/src/observers/map/mod.rs +++ b/libafl/src/observers/map/mod.rs @@ -5,6 +5,7 @@ use core::{ fmt::Debug, hash::{Hash, Hasher}, ops::{Deref, DerefMut}, + slice::{from_raw_parts, from_raw_parts_mut}, }; use libafl_bolts::{ @@ -405,23 +406,35 @@ pub trait VarLenMapObserver: MapObserver { fn size_mut(&mut self) -> &mut usize; } -/// Implementors guarantee the size of the map is constant at any point in time and equals N. +/// A variable length map, with a given size. pub trait HasVarLenMap { /// A map entry type Entry: PartialEq + Copy; - /// A mutable slice reference to the map. - /// The length of the map gives the maximum allocatable size. + /// A const ptr to the map. fn map_ptr(&self) -> *const Self::Entry; - /// A slice reference to the map. - /// The length of the map gives the maximum allocatable size. + /// A mutable slice reference to the map. + /// The length of the map gives the current size. + fn map_slice(&self) -> &[Self::Entry] { + unsafe { from_raw_parts(self.map_ptr(), *self.size_ptr()) } + } + + /// A mutable ptr to the map. fn map_ptr_mut(&mut self) -> *mut Self::Entry; + /// A slice reference to the map. + /// The length of the map gives the current size. + fn map_slice_mut(&mut self) -> &mut [Self::Entry] { + unsafe { from_raw_parts_mut(self.map_ptr_mut(), *self.size_ptr()) } + } + /// A reference to the size of the map. + /// The pointee should always be between 0 and `self.max_size()` fn size_ptr(&self) -> *const usize; /// A mutable reference to the size of the map. + /// The pointee should always be between 0 and `self.max_size()` fn size_ptr_mut(&mut self) -> *mut usize; /// The max size of the map @@ -488,7 +501,7 @@ impl HasVarLenMap for VarLenBytesMap { type Entry = u8; fn map_ptr(&self) -> *const Self::Entry { - self.map_ptr as *const Self::Entry + self.map_ptr.cast_const() } fn map_ptr_mut(&mut self) -> *mut Self::Entry { @@ -496,7 +509,7 @@ impl HasVarLenMap for VarLenBytesMap { } fn size_ptr(&self) -> *const usize { - self.size_ptr as *const usize + self.size_ptr.cast_const() } fn size_ptr_mut(&mut self) -> *mut usize { @@ -512,7 +525,7 @@ impl HasConstLenMap for ConstBytesMap { type Entry = u8; fn map_ptr(&self) -> *const Self::Entry { - self.map_ptr as *const u8 + self.map_ptr.cast_const() } fn map_ptr_mut(&mut self) -> *mut Self::Entry { diff --git a/libafl_bolts/src/lib.rs b/libafl_bolts/src/lib.rs index d51fd7d2d87..16bf6ebc0ac 100644 --- a/libafl_bolts/src/lib.rs +++ b/libafl_bolts/src/lib.rs @@ -190,7 +190,7 @@ use core::{ fmt::{self, Display}, num::{ParseIntError, TryFromIntError}, ops::{Deref, DerefMut}, - slice, time, + ptr, slice, time, }; #[cfg(feature = "std")] use std::{env::VarError, io}; @@ -1580,7 +1580,7 @@ where /// Cast any sized object into a byte array. pub fn any_as_bytes(any: &T) -> &[u8] { - unsafe { slice::from_raw_parts(any as *const T as *const u8, size_of::()) } + unsafe { slice::from_raw_parts(ptr::from_ref(any) as *const u8, size_of::()) } } /// Cast any sized object into a mutable byte array. @@ -1590,7 +1590,7 @@ pub fn any_as_bytes(any: &T) -> &[u8] { /// It is obviously dangerous, since it becomes possible to corrupt the /// input object. To use with care. pub unsafe fn any_as_bytes_mut(any: &mut T) -> &mut [u8] { - unsafe { slice::from_raw_parts_mut(any as *mut T as *mut u8, size_of::()) } + unsafe { slice::from_raw_parts_mut(ptr::from_mut(any) as *mut u8, size_of::()) } } /// Cast back a byte array into a given struct type. @@ -1602,6 +1602,7 @@ pub unsafe fn any_as_bytes_mut(any: &mut T) -> &mut [u8] { /// objects from unchecked memory. /// /// The safest way to use this function is in combination with [`any_as_bytes`]. +#[must_use] pub unsafe fn any_from_bytes(any_bytes: &[u8]) -> &T { unsafe { &*(any_bytes.as_ptr() as *const T) } } diff --git a/libafl_qemu/libafl_qemu_build/src/bindings.rs b/libafl_qemu/libafl_qemu_build/src/bindings.rs index 05aa2851eed..96ef2c39fc6 100644 --- a/libafl_qemu/libafl_qemu_build/src/bindings.rs +++ b/libafl_qemu/libafl_qemu_build/src/bindings.rs @@ -89,6 +89,7 @@ const WRAPPER_HEADER: &str = r#" #include "libafl/exit.h" #include "libafl/jit.h" #include "libafl/utils.h" +#include "libafl/tcg.h" #include "libafl/hook.h" diff --git a/libafl_qemu/libafl_qemu_sys/src/bindings/x86_64_stub_bindings.rs b/libafl_qemu/libafl_qemu_sys/src/bindings/x86_64_stub_bindings.rs index fdc5a877352..4398cf147bc 100644 --- a/libafl_qemu/libafl_qemu_sys/src/bindings/x86_64_stub_bindings.rs +++ b/libafl_qemu/libafl_qemu_sys/src/bindings/x86_64_stub_bindings.rs @@ -1,5 +1,5 @@ -/* 1.87.0-nightly */ -/* qemu git hash: 97bef506eed24ee8d0eda4a07c4419c55dae4acb */ +/* 1.88.0-nightly */ +/* qemu git hash: 67a5a1a31ebfd36e9bafa99c72bafab81d19ae51 */ /* automatically generated by rust-bindgen 0.71.1 */ use libc::siginfo_t; @@ -167,6 +167,7 @@ impl ::std::fmt::Debug for __IncompleteArrayField { fmt.write_str("__IncompleteArrayField") } } +pub const TB_JMP_OFFSET_INVALID: u32 = 65535; pub type __uint32_t = ::std::os::raw::c_uint; pub type __uid_t = ::std::os::raw::c_uint; pub type __off_t = ::std::os::raw::c_long; @@ -6653,6 +6654,8 @@ unsafe extern "C" { unsafe extern "C" { pub fn libafl_get_exit_reason() -> *mut libafl_exit_reason; } +pub type libafl_qemu_on_signal_hdlr = + ::std::option::Option; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct libafl_mapinfo { @@ -6745,6 +6748,9 @@ unsafe extern "C" { ret: *mut libafl_mapinfo, ) -> *mut IntervalTreeNode; } +unsafe extern "C" { + pub fn libafl_is_valid_addr(addr: target_ulong) -> bool; +} unsafe extern "C" { pub fn libafl_load_addr() -> u64; } @@ -6766,6 +6772,9 @@ unsafe extern "C" { unsafe extern "C" { pub fn libafl_set_return_on_crash(return_on_crash: bool); } +unsafe extern "C" { + pub fn libafl_set_on_signal_handler(hdlr: libafl_qemu_on_signal_hdlr); +} unsafe extern "C" { pub fn libafl_qemu_init(argc: ::std::os::raw::c_int, argv: *mut *mut ::std::os::raw::c_char); } @@ -8163,6 +8172,31 @@ unsafe extern "C" { unsafe extern "C" { pub fn libafl_qemu_host_page_size() -> usize; } +unsafe extern "C" { + pub fn libafl_tb_lookup( + cpu: *mut CPUState, + pc: vaddr, + cs_base: u64, + flags: u32, + cflags: u32, + ) -> *mut TranslationBlock; +} +unsafe extern "C" { + pub fn libafl_tb_gen_code( + cpu: *mut CPUState, + pc: vaddr, + cs_base: u64, + flags: u32, + cflags: ::std::os::raw::c_int, + ) -> *mut TranslationBlock; +} +unsafe extern "C" { + pub fn libafl_tb_add_jump( + tb: *mut TranslationBlock, + n: ::std::os::raw::c_int, + tb_next: *mut TranslationBlock, + ); +} unsafe extern "C" { pub fn libafl_tcg_gen_asan(addr: *mut TCGTemp, size: usize); } @@ -8214,7 +8248,14 @@ unsafe extern "C" { pub type libafl_block_pre_gen_cb = ::std::option::Option u64>; pub type libafl_block_post_gen_cb = ::std::option::Option< - unsafe extern "C" fn(data: u64, pc: target_ulong, block_length: target_ulong), + unsafe extern "C" fn( + data: u64, + pc: target_ulong, + block_length: target_ulong, + tb: *mut TranslationBlock, + last_tb: *mut TranslationBlock, + tb_exit: ::std::os::raw::c_int, + ), >; pub type libafl_block_exec_cb = ::std::option::Option; pub type libafl_block_jit_cb = @@ -8279,7 +8320,12 @@ unsafe extern "C" { pub fn libafl_qemu_hook_block_pre_run(pc: target_ulong); } unsafe extern "C" { - pub fn libafl_qemu_hook_block_post_run(tb: *mut TranslationBlock, pc: vaddr); + pub fn libafl_qemu_hook_block_post_run( + tb: *mut TranslationBlock, + last_tb: *mut TranslationBlock, + pc: vaddr, + tb_exit: ::std::os::raw::c_int, + ); } pub type libafl_cmp_gen_cb = ::std::option::Option u64>; diff --git a/libafl_qemu/runtime/libafl_qemu_stub_bindings.rs b/libafl_qemu/runtime/libafl_qemu_stub_bindings.rs index 80f3f558eb2..6b2aa0683a3 100644 --- a/libafl_qemu/runtime/libafl_qemu_stub_bindings.rs +++ b/libafl_qemu/runtime/libafl_qemu_stub_bindings.rs @@ -1,5 +1,5 @@ -/* 1.87.0-nightly */ -/* qemu git hash: 97bef506eed24ee8d0eda4a07c4419c55dae4acb */ +/* 1.88.0-nightly */ +/* qemu git hash: 67a5a1a31ebfd36e9bafa99c72bafab81d19ae51 */ /* automatically generated by rust-bindgen 0.71.1 */ #[repr(C)] diff --git a/libafl_qemu/runtime/nyx_stub_bindings.rs b/libafl_qemu/runtime/nyx_stub_bindings.rs index 8d38d388942..bbf481a8a8d 100644 --- a/libafl_qemu/runtime/nyx_stub_bindings.rs +++ b/libafl_qemu/runtime/nyx_stub_bindings.rs @@ -1,5 +1,5 @@ -/* 1.87.0-nightly */ -/* qemu git hash: 97bef506eed24ee8d0eda4a07c4419c55dae4acb */ +/* 1.88.0-nightly */ +/* qemu git hash: 67a5a1a31ebfd36e9bafa99c72bafab81d19ae51 */ /* automatically generated by rust-bindgen 0.71.1 */ #[repr(C)] diff --git a/libafl_qemu/src/modules/edges/mod.rs b/libafl_qemu/src/modules/edges/mod.rs index 8df8f387870..e8f76f3c23e 100644 --- a/libafl_qemu/src/modules/edges/mod.rs +++ b/libafl_qemu/src/modules/edges/mod.rs @@ -426,7 +426,7 @@ mod tests { }; StdEdgeCoverageModule::builder() - .map_observer(edges_observer.as_mut()) + .map(edges_observer.as_mut()) .build() .unwrap(); } diff --git a/libafl_qemu/src/qemu/usermode.rs b/libafl_qemu/src/qemu/usermode.rs index 312ce24d57d..25f76a8bd27 100644 --- a/libafl_qemu/src/qemu/usermode.rs +++ b/libafl_qemu/src/qemu/usermode.rs @@ -498,7 +498,7 @@ impl Qemu { flags: u32, cflags: u32, ) -> *mut TranslationBlock { - unsafe { libafl_qemu_sys::libafl_tb_lookup(cpu.ptr, pc, cs_base, flags, cflags) } + unsafe { libafl_qemu_sys::libafl_tb_lookup(cpu.cpu_ptr, pc, cs_base, flags, cflags) } } pub unsafe fn tb_gen_code( @@ -509,7 +509,7 @@ impl Qemu { flags: u32, cflags: i32, ) -> *mut TranslationBlock { - unsafe { libafl_qemu_sys::libafl_tb_gen_code(cpu.ptr, pc, cs_base, flags, cflags) } + unsafe { libafl_qemu_sys::libafl_tb_gen_code(cpu.cpu_ptr, pc, cs_base, flags, cflags) } } pub unsafe fn tb_add_jump(