Qemu signal refactoring (#2920)

* qemu signal refactoring

* udpate qemu

* clippy, moving things around

* update bindings

* nostd

* cfg

* fmt

* nostd

* clippy

* fmt

* aaa

* windowsssssss

* systemmode

* reimport fix

* remove llmp from replay mode

* lol

* fixer

---------

Co-authored-by: Dongjia "toka" Zhang <tokazerkje@outlook.com>
This commit is contained in:
Romain Malmain 2025-02-04 14:43:26 +01:00 committed by GitHub
parent defb475d28
commit 8398f8f99a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 395 additions and 171 deletions

View File

@ -48,19 +48,21 @@ mac_alias = "run_unix"
windows_alias = "unsupported" windows_alias = "unsupported"
[tasks.run_unix] [tasks.run_unix]
command = "cargo" script_runner = "@shell"
args = [ script = '''
"run", cargo build \
"--profile", --profile \
"${PROFILE}", ${PROFILE}
"./${FUZZER_NAME}",
"--", ${TARGET_DIR}/${PROFILE_DIR}/fuzzbench_qemu \
"--libafl-in", --libafl-in \
"../../inprocess/libfuzzer_libpng/corpus", ../../inprocess/libfuzzer_libpng/corpus \
"--libafl-out", --libafl-out \
"./out", ./out \
"./${FUZZER_NAME}", ./${FUZZER_NAME} \
] -- \
./${FUZZER_NAME}
'''
dependencies = ["harness"] dependencies = ["harness"]
# Run the fuzzer # Run the fuzzer

View File

@ -51,6 +51,7 @@ use libafl_qemu::{
// asan::{init_with_asan, QemuAsanHelper}, // asan::{init_with_asan, QemuAsanHelper},
modules::cmplog::{CmpLogModule, CmpLogObserver}, modules::cmplog::{CmpLogModule, CmpLogObserver},
modules::edges::StdEdgeCoverageModule, modules::edges::StdEdgeCoverageModule,
modules::AsanModule,
Emulator, Emulator,
GuestReg, GuestReg,
//snapshot::QemuSnapshotHelper, //snapshot::QemuSnapshotHelper,

View File

@ -375,6 +375,9 @@ echo "Profile: ${PROFILE}"
export QEMU_LAUNCHER=${TARGET_DIR}/${PROFILE_DIR}/qemu_launcher export QEMU_LAUNCHER=${TARGET_DIR}/${PROFILE_DIR}/qemu_launcher
./tests/injection/test.sh || exit 1 ./tests/injection/test.sh || exit 1
# complie again with simple mgr
cargo build --profile=${PROFILE} --features="simplemgr" --target-dir=${TARGET_DIR}
./tests/qasan/test.sh || exit 1 ./tests/qasan/test.sh || exit 1
''' '''
dependencies = ["build_unix"] dependencies = ["build_unix"]

View File

@ -86,7 +86,6 @@ impl Fuzzer {
// The shared memory allocator // The shared memory allocator
#[cfg(not(feature = "simplemgr"))] #[cfg(not(feature = "simplemgr"))]
let mut shmem_provider = StdShMemProvider::new()?; let mut shmem_provider = StdShMemProvider::new()?;
/* If we are running in verbose, don't provide a replacement stdout, otherwise, use /dev/null */ /* If we are running in verbose, don't provide a replacement stdout, otherwise, use /dev/null */
#[cfg(not(feature = "simplemgr"))] #[cfg(not(feature = "simplemgr"))]
let stdout = if self.options.verbose { let stdout = if self.options.verbose {
@ -97,34 +96,15 @@ impl Fuzzer {
let client = Client::new(&self.options); let client = Client::new(&self.options);
#[cfg(not(feature = "simplemgr"))] #[cfg(feature = "simplemgr")]
if self.options.rerun_input.is_some() { if self.options.rerun_input.is_some() {
// If we want to rerun a single input but we use a restarting mgr, we'll have to create a fake restarting mgr that doesn't actually restart. // only for simplemgr
// It's not pretty but better than recompiling with simplemgr. // DON'T USE LLMP HERE!!
// it doesn't work like that
// Just a random number, let's hope it's free :)
let broker_port = 13120;
let _fake_broker = LlmpBroker::create_attach_to_tcp(
shmem_provider.clone(),
tuple_list!(),
broker_port,
)
.unwrap();
// To rerun an input, instead of using a launcher, we create dummy parameters and run the client directly.
return client.run( return client.run(
None, None,
MonitorTypedEventManager::<_, M>::new( SimpleEventManager::new(monitor),
LlmpEventManagerBuilder::builder().build_on_port(
shmem_provider.clone(),
broker_port,
EventConfig::AlwaysUnique,
None,
Some(StateRestorer::new(
shmem_provider.new_shmem(0x1000).unwrap(),
)),
)?,
),
ClientDescription::new(0, 0, CoreId(0)), ClientDescription::new(0, 0, CoreId(0)),
); );
} }

View File

@ -25,7 +25,7 @@ tests_expected=(
"is 0 bytes to the right of the 10-byte chunk" "is 0 bytes to the right of the 10-byte chunk"
"is 1 bytes to the left of the 10-byte chunk" "is 1 bytes to the left of the 10-byte chunk"
"is 0 bytes inside the 10-byte chunk" "is 0 bytes inside the 10-byte chunk"
"is 0 bytes to the right of the 10-byte chunk" "Invalid 11 bytes write at"
"is 0 bytes inside the 10-byte chunk" "is 0 bytes inside the 10-byte chunk"
"Test-Limits - No Error" "Test-Limits - No Error"
) )

View File

@ -12,6 +12,8 @@ use core::{
#[cfg(all(target_os = "linux", feature = "std"))] #[cfg(all(target_os = "linux", feature = "std"))]
use libafl_bolts::current_time; use libafl_bolts::current_time;
#[cfg(all(unix, feature = "std"))]
use libafl_bolts::minibsod::{generate_minibsod_to_vec, BsodInfo};
#[cfg(all(unix, feature = "std", not(miri)))] #[cfg(all(unix, feature = "std", not(miri)))]
use libafl_bolts::os::unix_signals::setup_signal_handler; use libafl_bolts::os::unix_signals::setup_signal_handler;
#[cfg(all(windows, feature = "std"))] #[cfg(all(windows, feature = "std"))]
@ -21,8 +23,6 @@ use windows::Win32::System::Threading::{CRITICAL_SECTION, PTP_TIMER};
#[cfg(feature = "std")] #[cfg(feature = "std")]
use crate::executors::hooks::timer::TimerStruct; use crate::executors::hooks::timer::TimerStruct;
#[cfg(all(unix, feature = "std"))]
use crate::executors::hooks::unix::unix_signal_handler;
use crate::{ use crate::{
events::{EventFirer, EventRestarter}, events::{EventFirer, EventRestarter},
executors::{hooks::ExecutorHook, inprocess::HasInProcessHooks, Executor, HasObservers}, executors::{hooks::ExecutorHook, inprocess::HasInProcessHooks, Executor, HasObservers},
@ -30,6 +30,13 @@ use crate::{
state::{HasExecutions, HasSolutions}, state::{HasExecutions, HasSolutions},
Error, HasObjective, Error, HasObjective,
}; };
#[cfg(all(unix, feature = "std"))]
use crate::{
executors::{
hooks::unix::unix_signal_handler, inprocess::run_observers_and_save_state, ExitKind,
},
state::HasCorpus,
};
#[cfg(any(unix, windows))] #[cfg(any(unix, windows))]
use crate::{inputs::Input, observers::ObserversTuple, state::HasCurrentTestcase}; use crate::{inputs::Input, observers::ObserversTuple, state::HasCurrentTestcase};
@ -386,52 +393,111 @@ unsafe impl Sync for InProcessExecutorHandlerData {}
impl InProcessExecutorHandlerData { impl InProcessExecutorHandlerData {
/// # Safety /// # Safety
/// Only safe if not called twice and if the executor is not used from another borrow after this. /// Only safe if not called twice and if the executor is not used from another borrow after this.
#[cfg(any(unix, feature = "std"))] #[cfg(all(feature = "std", any(unix, windows)))]
pub(crate) unsafe fn executor_mut<'a, E>(&self) -> &'a mut E { pub(crate) unsafe fn executor_mut<'a, E>(&self) -> &'a mut E {
unsafe { (self.executor_ptr as *mut E).as_mut().unwrap() } unsafe { (self.executor_ptr as *mut E).as_mut().unwrap() }
} }
/// # Safety /// # Safety
/// Only safe if not called twice and if the state is not used from another borrow after this. /// Only safe if not called twice and if the state is not used from another borrow after this.
#[cfg(any(unix, feature = "std"))] #[cfg(all(feature = "std", any(unix, windows)))]
pub(crate) unsafe fn state_mut<'a, S>(&self) -> &'a mut S { pub(crate) unsafe fn state_mut<'a, S>(&self) -> &'a mut S {
unsafe { (self.state_ptr as *mut S).as_mut().unwrap() } unsafe { (self.state_ptr as *mut S).as_mut().unwrap() }
} }
/// # Safety /// # Safety
/// Only safe if not called twice and if the event manager is not used from another borrow after this. /// Only safe if not called twice and if the event manager is not used from another borrow after this.
#[cfg(any(unix, feature = "std"))] #[cfg(all(feature = "std", any(unix, windows)))]
pub(crate) unsafe fn event_mgr_mut<'a, EM>(&self) -> &'a mut EM { pub(crate) unsafe fn event_mgr_mut<'a, EM>(&self) -> &'a mut EM {
unsafe { (self.event_mgr_ptr as *mut EM).as_mut().unwrap() } unsafe { (self.event_mgr_ptr as *mut EM).as_mut().unwrap() }
} }
/// # Safety /// # Safety
/// Only safe if not called twice and if the fuzzer is not used from another borrow after this. /// Only safe if not called twice and if the fuzzer is not used from another borrow after this.
#[cfg(any(unix, feature = "std"))] #[cfg(all(feature = "std", any(unix, windows)))]
pub(crate) unsafe fn fuzzer_mut<'a, Z>(&self) -> &'a mut Z { pub(crate) unsafe fn fuzzer_mut<'a, Z>(&self) -> &'a mut Z {
unsafe { (self.fuzzer_ptr as *mut Z).as_mut().unwrap() } unsafe { (self.fuzzer_ptr as *mut Z).as_mut().unwrap() }
} }
/// # Safety /// # Safety
/// Only safe if not called concurrently. /// Only safe if not called concurrently.
#[cfg(any(unix, feature = "std"))] #[cfg(all(feature = "std", any(unix, windows)))]
pub(crate) unsafe fn take_current_input<'a, I>(&mut self) -> &'a I { pub(crate) unsafe fn take_current_input<'a, I>(&mut self) -> &'a I {
let r = unsafe { (self.current_input_ptr as *const I).as_ref().unwrap() }; let r = unsafe { (self.current_input_ptr as *const I).as_ref().unwrap() };
self.current_input_ptr = ptr::null(); self.current_input_ptr = ptr::null();
r r
} }
#[cfg(any(unix, feature = "std"))] #[cfg(all(feature = "std", any(unix, windows)))]
pub(crate) fn is_valid(&self) -> bool { pub(crate) fn is_valid(&self) -> bool {
!self.current_input_ptr.is_null() !self.current_input_ptr.is_null()
} }
#[cfg(any(unix, feature = "std"))] #[cfg(all(feature = "std", any(unix, windows)))]
pub(crate) fn set_in_handler(&mut self, v: bool) -> bool { pub(crate) fn set_in_handler(&mut self, v: bool) -> bool {
let old = self.in_handler; let old = self.in_handler;
self.in_handler = v; self.in_handler = v;
old old
} }
/// if data is valid, safely report a crash and return true.
/// return false otherwise.
///
/// # Safety
///
/// Should only be called to signal a crash in the target
#[cfg(all(unix, feature = "std"))]
pub unsafe fn maybe_report_crash<E, EM, I, OF, S, Z>(
&mut self,
bsod_info: Option<BsodInfo>,
) -> bool
where
E: Executor<EM, I, S, Z> + HasObservers,
E::Observers: ObserversTuple<I, S>,
EM: EventFirer<I, S> + EventRestarter<S>,
OF: Feedback<EM, I, E::Observers, S>,
S: HasExecutions + HasSolutions<I> + HasCorpus<I> + HasCurrentTestcase<I>,
Z: HasObjective<Objective = OF>,
I: Input + Clone,
{
if self.is_valid() {
let executor = self.executor_mut::<E>();
// disarms timeout in case of timeout
let state = self.state_mut::<S>();
let event_mgr = self.event_mgr_mut::<EM>();
let fuzzer = self.fuzzer_mut::<Z>();
let input = self.take_current_input::<I>();
log::error!("Target crashed!");
if let Some(bsod_info) = bsod_info {
let bsod = generate_minibsod_to_vec(
bsod_info.signal,
&bsod_info.siginfo,
bsod_info.ucontext.as_ref(),
);
if let Ok(bsod) = bsod {
if let Ok(r) = std::str::from_utf8(&bsod) {
log::error!("{}", r);
}
}
}
run_observers_and_save_state::<E, EM, I, OF, S, Z>(
executor,
state,
input,
fuzzer,
event_mgr,
ExitKind::Crash,
);
return true;
}
false
}
} }
/// Exception handling needs some nasty unsafe. /// Exception handling needs some nasty unsafe.

View File

@ -14,10 +14,6 @@ use core::{
use libafl_bolts::tuples::{tuple_list, RefIndexable}; use libafl_bolts::tuples::{tuple_list, RefIndexable};
#[cfg(any(unix, feature = "std"))]
use crate::executors::hooks::inprocess::GLOBAL_STATE;
#[cfg(any(unix, feature = "std"))]
use crate::ExecutionProcessor;
use crate::{ use crate::{
corpus::{Corpus, Testcase}, corpus::{Corpus, Testcase},
events::{Event, EventFirer, EventRestarter}, events::{Event, EventFirer, EventRestarter},
@ -436,45 +432,6 @@ pub fn run_observers_and_save_state<E, EM, I, OF, S, Z>(
log::info!("Bye!"); log::info!("Bye!");
} }
// TODO remove this after executor refactor and libafl qemu new executor
/// Expose a version of the crash handler that can be called from e.g. an emulator
///
/// # Safety
/// This will directly access `GLOBAL_STATE` and related data pointers
#[cfg(any(unix, feature = "std"))]
pub unsafe fn generic_inproc_crash_handler<E, EM, I, OF, S, Z>()
where
E: Executor<EM, I, S, Z> + HasObservers,
E::Observers: ObserversTuple<I, S>,
EM: EventFirer<I, S> + EventRestarter<S>,
OF: Feedback<EM, I, E::Observers, S>,
S: HasExecutions + HasSolutions<I> + HasCurrentTestcase<I>,
I: Input + Clone,
Z: HasObjective<Objective = OF> + ExecutionProcessor<EM, I, E::Observers, S>,
{
let data = &raw mut GLOBAL_STATE;
let in_handler = (*data).set_in_handler(true);
if (*data).is_valid() {
let executor = (*data).executor_mut::<E>();
let state = (*data).state_mut::<S>();
let event_mgr = (*data).event_mgr_mut::<EM>();
let fuzzer = (*data).fuzzer_mut::<Z>();
let input = (*data).take_current_input::<I>();
run_observers_and_save_state::<E, EM, I, OF, S, Z>(
executor,
state,
input,
fuzzer,
event_mgr,
ExitKind::Crash,
);
}
(*data).set_in_handler(in_handler);
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use libafl_bolts::{rands::XkcdRand, tuples::tuple_list}; use libafl_bolts::{rands::XkcdRand, tuples::tuple_list};

View File

@ -6,6 +6,8 @@ use core::mem::size_of;
use std::io::{BufWriter, Write}; use std::io::{BufWriter, Write};
#[cfg(any(target_os = "solaris", target_os = "illumos"))] #[cfg(any(target_os = "solaris", target_os = "illumos"))]
use std::process::Command; use std::process::Command;
#[cfg(unix)]
use std::vec::Vec;
#[cfg(unix)] #[cfg(unix)]
use libc::siginfo_t; use libc::siginfo_t;
@ -24,6 +26,18 @@ use windows::Win32::System::Diagnostics::Debug::{CONTEXT, EXCEPTION_POINTERS};
#[cfg(unix)] #[cfg(unix)]
use crate::os::unix_signals::{ucontext_t, Signal}; use crate::os::unix_signals::{ucontext_t, Signal};
/// Necessary info to print a mini-BSOD.
#[derive(Debug)]
#[cfg(unix)]
pub struct BsodInfo {
/// the signal
pub signal: Signal,
/// siginfo
pub siginfo: siginfo_t,
/// ucontext
pub ucontext: Option<ucontext_t>,
}
/// Write the content of all important registers /// Write the content of all important registers
#[cfg(all( #[cfg(all(
any(target_os = "linux", target_os = "android"), any(target_os = "linux", target_os = "android"),
@ -1110,6 +1124,24 @@ pub fn generate_minibsod<W: Write>(
write_minibsod(writer) write_minibsod(writer)
} }
/// Generates a mini-BSOD given a signal and context and dump it to a [`Vec`]
#[cfg(unix)]
pub fn generate_minibsod_to_vec(
signal: Signal,
siginfo: &siginfo_t,
ucontext: Option<&ucontext_t>,
) -> Result<Vec<u8>, std::io::Error> {
let mut bsod = Vec::new();
{
let mut writer = BufWriter::new(&mut bsod);
generate_minibsod(&mut writer, signal, siginfo, ucontext)?;
writer.flush()?;
}
Ok(bsod)
}
/// Generates a mini-BSOD given an `EXCEPTION_POINTERS` structure. /// Generates a mini-BSOD given an `EXCEPTION_POINTERS` structure.
#[cfg(windows)] #[cfg(windows)]
#[expect(clippy::non_ascii_literal, clippy::not_unsafe_ptr_arg_deref)] #[expect(clippy::non_ascii_literal, clippy::not_unsafe_ptr_arg_deref)]

View File

@ -11,7 +11,7 @@ use crate::cargo_add_rpath;
pub const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; pub const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
pub const QEMU_DIRNAME: &str = "qemu-libafl-bridge"; pub const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
pub const QEMU_REVISION: &str = "7e0dc68430c509ad50c6b0c9887f7e642a4bba2d"; pub const QEMU_REVISION: &str = "695657e4f3f408c34b146d5191b102d5eb99b74b";
pub struct BuildResult { pub struct BuildResult {
pub qemu_path: PathBuf, pub qemu_path: PathBuf,

View File

@ -1,5 +1,5 @@
/* 1.86.0-nightly */ /* 1.86.0-nightly */
/* qemu git hash: 3f7b2d86635aaf03818aaec1d285dba88255f831 */ /* qemu git hash: 695657e4f3f408c34b146d5191b102d5eb99b74b */
/* automatically generated by rust-bindgen 0.71.1 */ /* automatically generated by rust-bindgen 0.71.1 */
use libc::siginfo_t; use libc::siginfo_t;
@ -6315,15 +6315,42 @@ impl Default for libafl_mapinfo {
} }
} }
} }
unsafe extern "C" { #[repr(C)]
pub static mut libafl_dump_core_hook: #[derive(Debug, Default, Copy, Clone)]
::std::option::Option<unsafe extern "C" fn(host_sig: ::std::os::raw::c_int)>; pub struct libafl_qemu_sig_ctx {
pub in_qemu_sig_hdlr: bool,
pub is_target_signal: bool,
} }
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
["Size of libafl_qemu_sig_ctx"][::std::mem::size_of::<libafl_qemu_sig_ctx>() - 2usize];
["Alignment of libafl_qemu_sig_ctx"][::std::mem::align_of::<libafl_qemu_sig_ctx>() - 1usize];
["Offset of field: libafl_qemu_sig_ctx::in_qemu_sig_hdlr"]
[::std::mem::offset_of!(libafl_qemu_sig_ctx, in_qemu_sig_hdlr) - 0usize];
["Offset of field: libafl_qemu_sig_ctx::is_target_signal"]
[::std::mem::offset_of!(libafl_qemu_sig_ctx, is_target_signal) - 1usize];
};
unsafe extern "C" { unsafe extern "C" {
pub static mut libafl_force_dfl: ::std::os::raw::c_int; pub static mut libafl_force_dfl: ::std::os::raw::c_int;
} }
unsafe extern "C" { unsafe extern "C" {
pub fn libafl_dump_core_exec(signal: ::std::os::raw::c_int); pub fn libafl_qemu_native_signal_handler(
host_sig: ::std::os::raw::c_int,
info: *mut siginfo_t,
puc: *mut ::std::os::raw::c_void,
);
}
unsafe extern "C" {
pub fn libafl_qemu_signal_context() -> *mut libafl_qemu_sig_ctx;
}
unsafe extern "C" {
pub fn libafl_set_in_target_signal_ctx();
}
unsafe extern "C" {
pub fn libafl_set_in_host_signal_ctx();
}
unsafe extern "C" {
pub fn libafl_unset_in_signal_ctx();
} }
unsafe extern "C" { unsafe extern "C" {
pub fn libafl_qemu_handle_crash( pub fn libafl_qemu_handle_crash(

View File

@ -1,5 +1,5 @@
/* 1.86.0-nightly */ /* 1.86.0-nightly */
/* qemu git hash: 3f7b2d86635aaf03818aaec1d285dba88255f831 */ /* qemu git hash: 695657e4f3f408c34b146d5191b102d5eb99b74b */
/* automatically generated by rust-bindgen 0.71.1 */ /* automatically generated by rust-bindgen 0.71.1 */
pub const LIBAFL_SYNC_EXIT_OPCODE: u32 = 1727150607; pub const LIBAFL_SYNC_EXIT_OPCODE: u32 = 1727150607;

View File

@ -1,5 +1,5 @@
/* 1.86.0-nightly */ /* 1.86.0-nightly */
/* qemu git hash: 3f7b2d86635aaf03818aaec1d285dba88255f831 */ /* qemu git hash: 695657e4f3f408c34b146d5191b102d5eb99b74b */
/* automatically generated by rust-bindgen 0.71.1 */ /* automatically generated by rust-bindgen 0.71.1 */
#[repr(C)] #[repr(C)]

View File

@ -73,7 +73,7 @@ macro_rules! hook_to_repr {
static mut EMULATOR_MODULES: *mut () = ptr::null_mut(); static mut EMULATOR_MODULES: *mut () = ptr::null_mut();
#[cfg(feature = "usermode")] #[cfg(feature = "usermode")]
pub extern "C" fn crash_hook_wrapper<ET, I, S>(target_sig: i32) pub extern "C" fn run_target_crash_hooks<ET, I, S>(target_sig: i32)
where where
ET: EmulatorModuleTuple<I, S>, ET: EmulatorModuleTuple<I, S>,
S: Unpin, S: Unpin,
@ -974,8 +974,6 @@ where
pub fn crash_function(&mut self, hook: CrashHookFn<ET, I, S>) { pub fn crash_function(&mut self, hook: CrashHookFn<ET, I, S>) {
// # Safety // # Safety
// Will cast the valid hook to a ptr. // Will cast the valid hook to a ptr.
self.qemu_hooks
.set_crash_hook(crash_hook_wrapper::<ET, I, S>);
self.hook_collection self.hook_collection
.crash_hooks .crash_hooks
.push(HookRepr::Function(hook as *const libc::c_void)); .push(HookRepr::Function(hook as *const libc::c_void));
@ -985,8 +983,6 @@ where
// # Safety // # Safety
// Will cast the hook to a [`FatPtr`]. // Will cast the hook to a [`FatPtr`].
unsafe { unsafe {
self.qemu_hooks
.set_crash_hook(crash_hook_wrapper::<ET, I, S>);
self.hook_collection self.hook_collection
.crash_hooks .crash_hooks
.push(HookRepr::Closure(transmute(hook))); .push(HookRepr::Closure(transmute(hook)));

View File

@ -4,11 +4,13 @@ use core::{
fmt::{self, Debug, Formatter}, fmt::{self, Debug, Formatter},
time::Duration, time::Duration,
}; };
#[cfg(feature = "usermode")]
use std::ptr;
#[cfg(feature = "systemmode")] #[cfg(feature = "systemmode")]
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(feature = "usermode")]
use std::{ptr, str};
#[cfg(feature = "usermode")]
use libafl::state::HasCorpus;
use libafl::{ use libafl::{
events::{EventFirer, EventRestarter}, events::{EventFirer, EventRestarter},
executors::{ executors::{
@ -24,6 +26,8 @@ use libafl::{
state::{HasCurrentTestcase, HasExecutions, HasSolutions}, state::{HasCurrentTestcase, HasExecutions, HasSolutions},
Error, ExecutionProcessor, HasScheduler, Error, ExecutionProcessor, HasScheduler,
}; };
#[cfg(feature = "usermode")]
use libafl_bolts::minibsod;
#[cfg(feature = "fork")] #[cfg(feature = "fork")]
use libafl_bolts::shmem::ShMemProvider; use libafl_bolts::shmem::ShMemProvider;
use libafl_bolts::{ use libafl_bolts::{
@ -32,15 +36,11 @@ use libafl_bolts::{
}; };
#[cfg(feature = "systemmode")] #[cfg(feature = "systemmode")]
use libafl_qemu_sys::libafl_exit_request_timeout; use libafl_qemu_sys::libafl_exit_request_timeout;
#[cfg(feature = "usermode")]
use libafl_qemu_sys::libafl_qemu_handle_crash;
use libc::siginfo_t; use libc::siginfo_t;
#[cfg(feature = "usermode")]
use crate::EmulatorModules;
#[cfg(feature = "usermode")]
use crate::Qemu;
use crate::{command::CommandManager, modules::EmulatorModuleTuple, Emulator, EmulatorDriver}; use crate::{command::CommandManager, modules::EmulatorModuleTuple, Emulator, EmulatorDriver};
#[cfg(feature = "usermode")]
use crate::{run_target_crash_hooks, EmulatorModules, Qemu, QemuSignalContext};
type EmulatorInProcessExecutor<'a, C, CM, ED, EM, ET, H, I, OT, S, SM, Z> = type EmulatorInProcessExecutor<'a, C, CM, ED, EM, ET, H, I, OT, S, SM, Z> =
StatefulInProcessExecutor<'a, EM, Emulator<C, CM, ED, ET, I, S, SM>, H, I, OT, S, Z>; StatefulInProcessExecutor<'a, EM, Emulator<C, CM, ED, ET, I, S, SM>, H, I, OT, S, Z>;
@ -50,31 +50,103 @@ pub struct QemuExecutor<'a, C, CM, ED, EM, ET, H, I, OT, S, SM, Z> {
first_exec: bool, first_exec: bool,
} }
/// `LibAFL` QEMU main crash handler.
/// Be careful, it can come after QEMU's native crash handler if a signal is caught by QEMU but
/// not by `LibAFL`.
///
/// Signals handlers can be nested.
///
/// # Safety /// # Safety
/// ///
/// This should be used as a crash handler, and nothing else. /// This should be used as a crash handler, and nothing else.
#[cfg(feature = "usermode")] #[cfg(feature = "usermode")]
unsafe fn inproc_qemu_crash_handler<ET, I, S>( unsafe fn inproc_qemu_crash_handler<E, EM, ET, I, OF, S, Z>(
signal: Signal, signal: Signal,
info: &mut siginfo_t, info: &mut siginfo_t,
mut context: Option<&mut ucontext_t>, mut context: Option<&mut ucontext_t>,
_data: &mut InProcessExecutorHandlerData, data: &mut InProcessExecutorHandlerData,
) where ) where
ET: EmulatorModuleTuple<I, S>, ET: EmulatorModuleTuple<I, S>,
I: Unpin, E: Executor<EM, I, S, Z> + HasObservers,
S: Unpin, E::Observers: ObserversTuple<I, S>,
EM: EventFirer<I, S> + EventRestarter<S>,
OF: Feedback<EM, I, E::Observers, S>,
S: HasExecutions + HasSolutions<I> + HasCorpus<I> + HasCurrentTestcase<I> + Unpin,
Z: HasObjective<Objective = OF>,
I: Input + Clone + Unpin,
{ {
log::debug!("QEMU signal handler has been triggered (signal {signal})");
let puc = match &mut context { let puc = match &mut context {
Some(v) => ptr::from_mut::<ucontext_t>(*v) as *mut c_void, Some(v) => ptr::from_mut::<ucontext_t>(*v) as *mut c_void,
None => ptr::null_mut(), None => ptr::null_mut(),
}; };
// run modules' crash callback if let Some(qemu) = Qemu::get() {
if let Some(emulator_modules) = EmulatorModules::<ET, I, S>::emulator_modules_mut() { // QEMU is already initialized, we have to route the signal to QEMU's handler or
emulator_modules.modules_mut().on_crash_all(); // consider it as a host (i.e. fuzzer) signal
if qemu.is_running() {
// QEMU is running, we must determine if we are coming from qemu's signal handler or not
log::debug!("Signal has been triggered while QEMU was running");
match qemu.signal_ctx() {
QemuSignalContext::OutOfQemuSignalHandler => {
// we did not run QEMU's signal handler, run it not
log::debug!("It's a simple signal, let QEMU handle it first");
qemu.run_signal_handler(signal.into(), info, puc);
// if we are there, we can safely to execution
return;
}
QemuSignalContext::InQemuSignalHandlerHost => {
// we are running in a nested signal handling
// and the signal is a host QEMU signal
let si_addr = { info.si_addr() as usize };
log::error!("QEMU Host crash crashed at addr 0x{si_addr:x}... Bug in QEMU or Emulator modules? Exiting.\n");
if let Some(cpu) = qemu.current_cpu() {
eprint!("QEMU Context:\n{}", cpu.display_context());
}
}
QemuSignalContext::InQemuSignalHandlerTarget => {
// we are running in a nested signal handler and the signal is a target signal.
// run qemu hooks then report the crash.
log::debug!("QEMU Target signal received that should be handled by host. Most likely a target crash.");
log::debug!("Running crash hooks.");
run_target_crash_hooks::<ET, I, S>(signal.into());
assert!(data.maybe_report_crash::<E, EM, I, OF, S, Z>(None));
if let Some(cpu) = qemu.current_cpu() {
eprint!("QEMU Context:\n{}", cpu.display_context());
}
}
}
} else {
// qemu is not running, it is a bug in LibAFL
let si_addr = { info.si_addr() as usize };
log::error!("The fuzzer crashed at addr 0x{si_addr:x}... Bug in the fuzzer? Exiting.");
let bsod = minibsod::generate_minibsod_to_vec(signal, info, context.as_deref());
if let Ok(bsod) = bsod {
if let Ok(bsod_str) = str::from_utf8(&bsod) {
log::error!("\n{}", bsod_str);
} else {
log::error!("convert minibsod to string failed");
}
} else {
log::error!("generate_minibsod failed");
}
}
} }
libafl_qemu_handle_crash(signal as i32, info, puc); libc::_exit(128 + (signal as i32));
} }
#[cfg(feature = "systemmode")] #[cfg(feature = "systemmode")]
@ -178,34 +250,7 @@ where
#[cfg(feature = "usermode")] #[cfg(feature = "usermode")]
{ {
inner.inprocess_hooks_mut().crash_handler = inner.inprocess_hooks_mut().crash_handler =
inproc_qemu_crash_handler::<ET, I, S> as *const c_void; inproc_qemu_crash_handler::<Self, EM, ET, I, OF, S, Z> as *const c_void;
let handler =
|qemu: Qemu, _emulator_modules: &mut EmulatorModules<ET, I, S>, host_sig| {
eprintln!("Crashed with signal {host_sig}");
unsafe {
libafl::executors::inprocess::generic_inproc_crash_handler::<
Self,
EM,
I,
OF,
S,
Z,
>();
}
if let Some(cpu) = qemu.current_cpu() {
eprint!("Context:\n{}", cpu.display_context());
}
};
// # Safety
// We assume our crash handlers to be safe/quit after execution.
unsafe {
inner
.exposed_executor_state_mut()
.modules_mut()
.crash_closure(Box::new(handler));
}
} }
inner.inprocess_hooks_mut().timeout_handler = inproc_qemu_timeout_handler::< inner.inprocess_hooks_mut().timeout_handler = inproc_qemu_timeout_handler::<

View File

@ -9,12 +9,12 @@ use std::{
fs, fs,
path::PathBuf, path::PathBuf,
pin::Pin, pin::Pin,
process,
sync::Mutex, sync::Mutex,
}; };
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use libafl::{executors::ExitKind, observers::ObserversTuple}; use libafl::{executors::ExitKind, observers::ObserversTuple};
use libafl_bolts::os::unix_signals::Signal;
use libafl_qemu_sys::GuestAddr; use libafl_qemu_sys::GuestAddr;
use libc::{ use libc::{
c_void, MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_NORESERVE, MAP_PRIVATE, PROT_READ, PROT_WRITE, c_void, MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_NORESERVE, MAP_PRIVATE, PROT_READ, PROT_WRITE,
@ -73,6 +73,17 @@ pub struct AsanGiovese {
pub dirty_shadow: Mutex<HashSet<GuestAddr>>, pub dirty_shadow: Mutex<HashSet<GuestAddr>>,
pub saved_shadow: HashMap<GuestAddr, Vec<i8>>, pub saved_shadow: HashMap<GuestAddr, Vec<i8>>,
pub snapshot_shadow: bool, pub snapshot_shadow: bool,
pub target_crash: AsanTargetCrash,
pub error_found: bool,
}
pub struct AsanModuleBuilder {
env: Vec<(String, String)>,
detect_leaks: bool,
snapshot: bool,
filter: StdAddressFilter,
error_callback: Option<AsanErrorCallback>,
target_crash: AsanTargetCrash,
} }
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy)] #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy)]
@ -132,12 +143,11 @@ pub enum AsanError {
Signal(i32), Signal(i32),
} }
pub struct AsanModuleBuilder { #[derive(Clone, Debug)]
env: Vec<(String, String)>, pub enum AsanTargetCrash {
detect_leaks: bool, Never,
snapshot: bool, OnFirstError,
filter: StdAddressFilter, OnTargetStop,
error_callback: Option<AsanErrorCallback>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -239,6 +249,7 @@ impl AsanModuleBuilder {
snapshot: bool, snapshot: bool,
filter: StdAddressFilter, filter: StdAddressFilter,
error_callback: Option<AsanErrorCallback>, error_callback: Option<AsanErrorCallback>,
target_crash: AsanTargetCrash,
) -> Self { ) -> Self {
Self { Self {
env, env,
@ -246,6 +257,7 @@ impl AsanModuleBuilder {
snapshot, snapshot,
filter, filter,
error_callback, error_callback,
target_crash,
} }
} }
@ -257,6 +269,7 @@ impl AsanModuleBuilder {
self.snapshot, self.snapshot,
self.filter, self.filter,
self.error_callback, self.error_callback,
self.target_crash,
) )
} }
@ -268,6 +281,7 @@ impl AsanModuleBuilder {
self.snapshot, self.snapshot,
self.filter, self.filter,
self.error_callback, self.error_callback,
self.target_crash,
) )
} }
@ -279,6 +293,7 @@ impl AsanModuleBuilder {
snapshot, snapshot,
self.filter, self.filter,
self.error_callback, self.error_callback,
self.target_crash,
) )
} }
@ -290,6 +305,7 @@ impl AsanModuleBuilder {
self.snapshot, self.snapshot,
filter, filter,
self.error_callback, self.error_callback,
self.target_crash,
) )
} }
@ -301,6 +317,7 @@ impl AsanModuleBuilder {
self.snapshot, self.snapshot,
self.filter, self.filter,
Some(callback), Some(callback),
self.target_crash,
) )
} }
@ -318,6 +335,19 @@ impl AsanModuleBuilder {
self.snapshot, self.snapshot,
self.filter, self.filter,
Some(AsanErrorCallback::report()), Some(AsanErrorCallback::report()),
self.target_crash,
)
}
#[must_use]
pub fn target_crash(self, target_crash: AsanTargetCrash) -> Self {
Self::new(
self.env,
self.detect_leaks,
self.snapshot,
self.filter,
self.error_callback,
target_crash,
) )
} }
@ -329,6 +359,7 @@ impl AsanModuleBuilder {
self.snapshot, self.snapshot,
self.filter, self.filter,
self.error_callback, self.error_callback,
self.target_crash,
) )
} }
} }
@ -338,8 +369,14 @@ impl Default for AsanModuleBuilder {
let env = env::vars() let env = env::vars()
.filter(|(k, _v)| k != "LD_LIBRARY_PATH") .filter(|(k, _v)| k != "LD_LIBRARY_PATH")
.collect::<Vec<(String, String)>>(); .collect::<Vec<(String, String)>>();
Self::new(
Self::new(env, false, true, StdAddressFilter::default(), None) env,
false,
true,
StdAddressFilter::default(),
None,
AsanTargetCrash::OnFirstError,
)
} }
} }
@ -356,6 +393,7 @@ impl AsanModule {
snapshot: bool, snapshot: bool,
filter: StdAddressFilter, filter: StdAddressFilter,
error_callback: Option<AsanErrorCallback>, error_callback: Option<AsanErrorCallback>,
target_crash: AsanTargetCrash,
) -> Self { ) -> Self {
let mut rt = AsanGiovese::new(); let mut rt = AsanGiovese::new();
@ -364,6 +402,8 @@ impl AsanModule {
rt.set_error_callback(cb); rt.set_error_callback(cb);
} }
rt.set_target_crash(target_crash);
Self { Self {
env: env.to_vec(), env: env.to_vec(),
enabled: true, enabled: true,
@ -488,6 +528,8 @@ impl AsanGiovese {
dirty_shadow: Mutex::new(HashSet::default()), dirty_shadow: Mutex::new(HashSet::default()),
saved_shadow: HashMap::default(), saved_shadow: HashMap::default(),
snapshot_shadow: true, // By default, track the dirty shadow pages snapshot_shadow: true, // By default, track the dirty shadow pages
target_crash: AsanTargetCrash::OnFirstError,
error_found: false,
}; };
Box::pin(res) Box::pin(res)
} }
@ -551,6 +593,10 @@ impl AsanGiovese {
self.snapshot_shadow = snapshot_shadow; self.snapshot_shadow = snapshot_shadow;
} }
fn set_target_crash(&mut self, target_crash: AsanTargetCrash) {
self.target_crash = target_crash;
}
#[inline] #[inline]
#[must_use] #[must_use]
pub fn is_invalid_access<const N: usize>(qemu: Qemu, addr: GuestAddr) -> bool { pub fn is_invalid_access<const N: usize>(qemu: Qemu, addr: GuestAddr) -> bool {
@ -708,8 +754,12 @@ impl AsanGiovese {
if let Some(mut cb) = self.error_callback.take() { if let Some(mut cb) = self.error_callback.take() {
cb.call(self, qemu, pc, error); cb.call(self, qemu, pc, error);
self.error_callback = Some(cb); self.error_callback = Some(cb);
} else { }
process::abort();
if let AsanTargetCrash::OnFirstError = self.target_crash {
unsafe {
qemu.target_signal(Signal::SigSegmentationFault);
}
} }
} }
@ -1037,6 +1087,8 @@ where
) where ) where
ET: EmulatorModuleTuple<I, S>, ET: EmulatorModuleTuple<I, S>,
{ {
self.rt.error_found = false;
if self.empty { if self.empty {
self.rt.snapshot(qemu); self.rt.snapshot(qemu);
self.empty = false; self.empty = false;
@ -1055,6 +1107,12 @@ where
ET: EmulatorModuleTuple<I, S>, ET: EmulatorModuleTuple<I, S>,
OT: ObserversTuple<I, S>, OT: ObserversTuple<I, S>,
{ {
if let AsanTargetCrash::OnTargetStop = self.rt.target_crash {
unsafe {
qemu.target_signal(Signal::SigSegmentationFault);
}
}
if self.reset(qemu) == AsanRollback::HasLeaks { if self.reset(qemu) == AsanRollback::HasLeaks {
*exit_kind = ExitKind::Crash; *exit_kind = ExitKind::Crash;
} }

View File

@ -6,8 +6,6 @@
use core::{ffi::c_void, fmt::Debug, mem::transmute, ptr}; use core::{ffi::c_void, fmt::Debug, mem::transmute, ptr};
use libafl::executors::hooks::inprocess::inprocess_get_state; use libafl::executors::hooks::inprocess::inprocess_get_state;
#[cfg(feature = "usermode")]
use libafl_qemu_sys::libafl_dump_core_hook;
use libafl_qemu_sys::{CPUArchStatePtr, CPUStatePtr, FatPtr, GuestAddr, GuestUsize}; use libafl_qemu_sys::{CPUArchStatePtr, CPUStatePtr, FatPtr, GuestAddr, GuestUsize};
#[cfg(feature = "python")] #[cfg(feature = "python")]
use pyo3::{pyclass, pymethods, FromPyObject}; use pyo3::{pyclass, pymethods, FromPyObject};
@ -1242,13 +1240,6 @@ impl QemuHooks {
PostSyscallHookId(num) PostSyscallHookId(num)
} }
} }
#[expect(clippy::unused_self)]
pub(crate) fn set_crash_hook(self, callback: extern "C" fn(i32)) {
unsafe {
libafl_dump_core_hook = Some(callback);
}
}
} }
#[cfg(feature = "python")] #[cfg(feature = "python")]

View File

@ -54,6 +54,7 @@ pub use hooks::*;
use libafl_bolts::{vec_init, AsSliceMut}; use libafl_bolts::{vec_init, AsSliceMut};
static mut QEMU_IS_INITIALIZED: bool = false; static mut QEMU_IS_INITIALIZED: bool = false;
static mut QEMU_IS_RUNNING: bool = false;
pub(super) static QEMU_CONFIG: OnceLock<QemuConfig> = OnceLock::new(); pub(super) static QEMU_CONFIG: OnceLock<QemuConfig> = OnceLock::new();
@ -631,7 +632,9 @@ impl Qemu {
/// Should, in general, be safe to call. /// Should, in general, be safe to call.
/// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system. /// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system.
pub unsafe fn run(&self) -> Result<QemuExitReason, QemuExitError> { pub unsafe fn run(&self) -> Result<QemuExitReason, QemuExitError> {
QEMU_IS_RUNNING = true;
self.run_inner(); self.run_inner();
QEMU_IS_RUNNING = false;
let exit_reason = unsafe { libafl_get_exit_reason() }; let exit_reason = unsafe { libafl_get_exit_reason() };
if exit_reason.is_null() { if exit_reason.is_null() {
@ -902,6 +905,11 @@ impl Qemu {
pub fn host_page_size(&self) -> usize { pub fn host_page_size(&self) -> usize {
unsafe { libafl_qemu_sys::libafl_qemu_host_page_size() } unsafe { libafl_qemu_sys::libafl_qemu_host_page_size() }
} }
#[must_use]
pub fn is_running(&self) -> bool {
unsafe { QEMU_IS_RUNNING }
}
} }
impl ArchExtras for Qemu { impl ArchExtras for Qemu {

View File

@ -1,19 +1,20 @@
use std::{ use std::{
mem::MaybeUninit, ops::Range, ptr::copy_nonoverlapping, slice::from_raw_parts_mut, ffi::c_void, mem::MaybeUninit, ops::Range, ptr::copy_nonoverlapping, slice::from_raw_parts_mut,
str::from_utf8_unchecked_mut, str::from_utf8_unchecked_mut,
}; };
use libafl_bolts::os::unix_signals::Signal;
use libafl_qemu_sys::{ use libafl_qemu_sys::{
exec_path, free_self_maps, guest_base, libafl_force_dfl, libafl_get_brk, 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_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, libafl_set_brk, mmap_next_start, pageflags_get_root, read_self_maps, GuestAddr, GuestUsize,
IntervalTreeNode, IntervalTreeRoot, MapInfo, MmapPerms, VerifyAccess, IntervalTreeNode, IntervalTreeRoot, MapInfo, MmapPerms, VerifyAccess,
}; };
use libc::{c_int, c_uchar, strlen}; use libc::{c_int, c_uchar, siginfo_t, strlen};
#[cfg(feature = "python")] #[cfg(feature = "python")]
use pyo3::{pyclass, pymethods, IntoPyObject, Py, PyRef, PyRefMut, Python}; use pyo3::{pyclass, pymethods, IntoPyObject, Py, PyRef, PyRefMut, Python};
use crate::{Qemu, CPU}; use crate::{qemu::QEMU_IS_RUNNING, Qemu, CPU};
#[cfg_attr(feature = "python", pyclass(unsendable))] #[cfg_attr(feature = "python", pyclass(unsendable))]
pub struct GuestMaps { pub struct GuestMaps {
@ -33,6 +34,15 @@ pub struct ImageInfo {
pub exec_stack: bool, pub exec_stack: bool,
} }
pub enum QemuSignalContext {
/// We are not in QEMU's signal handler, no signal is being propagated.
OutOfQemuSignalHandler,
/// We are propagating a host signal from QEMU signal handler.
InQemuSignalHandlerHost,
/// We are propagating a target signal from QEMU signal handler
InQemuSignalHandlerTarget,
}
// Consider a private new only for Emulator // Consider a private new only for Emulator
impl GuestMaps { impl GuestMaps {
#[must_use] #[must_use]
@ -296,6 +306,54 @@ impl Qemu {
Err(format!("Failed to unmap {addr}")) Err(format!("Failed to unmap {addr}"))
} }
} }
#[must_use]
pub fn signal_ctx(&self) -> QemuSignalContext {
unsafe {
let qemu_signal_ctx = *libafl_qemu_sys::libafl_qemu_signal_context();
if qemu_signal_ctx.in_qemu_sig_hdlr {
if qemu_signal_ctx.is_target_signal {
QemuSignalContext::InQemuSignalHandlerTarget
} else {
QemuSignalContext::InQemuSignalHandlerHost
}
} else {
QemuSignalContext::OutOfQemuSignalHandler
}
}
}
/// Runs QEMU signal's handler
/// If it is already running, returns true.
/// In that case, it would most likely mean we are in a signal loop.
///
/// # Safety
///
/// Run QEMU's native signal handler.
///
/// Needlessly to say, it should be used very carefully.
/// It will run QEMU's signal handler, and maybe propagate new signals.
pub(crate) unsafe fn run_signal_handler(
&self,
host_sig: c_int,
info: *mut siginfo_t,
puc: *mut c_void,
) {
libafl_qemu_sys::libafl_qemu_native_signal_handler(host_sig, info, puc);
}
/// Emulate a signal coming from the target
///
/// # Safety
///
/// This may raise a signal to host. Some signals could have a funky behaviour.
/// SIGSEGV is safe to use.
pub unsafe fn target_signal(&self, signal: Signal) {
QEMU_IS_RUNNING = true;
libafl_qemu_sys::libafl_set_in_target_signal_ctx();
libc::raise(signal.into());
}
} }
#[cfg(feature = "python")] #[cfg(feature = "python")]