Support on_crash & on_timeout callbacks for libafl_qemu modules (#2620)

* support (unsafe) on_crash / on_timeout callbacks for modules

* use libc types in bindgen
This commit is contained in:
Romain Malmain 2024-10-21 17:59:04 +02:00 committed by GitHub
parent f0da4d15da
commit d96d833760
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 187 additions and 144 deletions

View File

@ -181,6 +181,8 @@ pub fn generate(
.allowlist_function("qemu_main_loop") .allowlist_function("qemu_main_loop")
.allowlist_function("qemu_cleanup") .allowlist_function("qemu_cleanup")
.blocklist_function("main_loop_wait") // bindgen issue #1313 .blocklist_function("main_loop_wait") // bindgen issue #1313
.blocklist_type("siginfo_t")
.raw_line("use libc::siginfo_t;")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new())); .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));
// arch specific functions // arch specific functions

View File

@ -1,7 +1,9 @@
/* 1.83.0-nightly */ /* 1.84.0-nightly */
/* qemu git hash: d6637939526f453c69f4c6bfe4635feb5dc5c0be */ /* qemu git hash: 805b14ffc44999952562e8f219d81c21a4fa50b9 */
/* automatically generated by rust-bindgen 0.70.1 */ /* automatically generated by rust-bindgen 0.70.1 */
use libc::siginfo_t;
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] #[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct __BindgenBitfieldUnit<Storage> { pub struct __BindgenBitfieldUnit<Storage> {
@ -516,15 +518,6 @@ impl ::std::fmt::Debug for sigval {
pub type __sigval_t = sigval; pub type __sigval_t = sigval;
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct siginfo_t {
pub si_signo: ::std::os::raw::c_int,
pub si_errno: ::std::os::raw::c_int,
pub si_code: ::std::os::raw::c_int,
pub __pad0: ::std::os::raw::c_int,
pub _sifields: siginfo_t__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union siginfo_t__bindgen_ty_1 { pub union siginfo_t__bindgen_ty_1 {
pub _pad: [::std::os::raw::c_int; 28usize], pub _pad: [::std::os::raw::c_int; 28usize],
pub _kill: siginfo_t__bindgen_ty_1__bindgen_ty_1, pub _kill: siginfo_t__bindgen_ty_1__bindgen_ty_1,
@ -827,31 +820,6 @@ impl ::std::fmt::Debug for siginfo_t__bindgen_ty_1 {
write!(f, "siginfo_t__bindgen_ty_1 {{ union }}") write!(f, "siginfo_t__bindgen_ty_1 {{ union }}")
} }
} }
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
["Size of siginfo_t"][::std::mem::size_of::<siginfo_t>() - 128usize];
["Alignment of siginfo_t"][::std::mem::align_of::<siginfo_t>() - 8usize];
["Offset of field: siginfo_t::si_signo"][::std::mem::offset_of!(siginfo_t, si_signo) - 0usize];
["Offset of field: siginfo_t::si_errno"][::std::mem::offset_of!(siginfo_t, si_errno) - 4usize];
["Offset of field: siginfo_t::si_code"][::std::mem::offset_of!(siginfo_t, si_code) - 8usize];
["Offset of field: siginfo_t::__pad0"][::std::mem::offset_of!(siginfo_t, __pad0) - 12usize];
["Offset of field: siginfo_t::_sifields"]
[::std::mem::offset_of!(siginfo_t, _sifields) - 16usize];
};
impl Default for siginfo_t {
fn default() -> Self {
let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
s.assume_init()
}
}
}
impl ::std::fmt::Debug for siginfo_t {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write ! (f , "siginfo_t {{ si_signo: {:?}, si_errno: {:?}, si_code: {:?}, __pad0: {:?}, _sifields: {:?} }}" , self . si_signo , self . si_errno , self . si_code , self . __pad0 , self . _sifields)
}
}
pub type guint8 = ::std::os::raw::c_uchar; pub type guint8 = ::std::os::raw::c_uchar;
pub type gchar = ::std::os::raw::c_char; pub type gchar = ::std::os::raw::c_char;
pub type guint = ::std::os::raw::c_uint; pub type guint = ::std::os::raw::c_uint;

View File

@ -1,5 +1,5 @@
/* 1.83.0-nightly */ /* 1.84.0-nightly */
/* qemu git hash: d6637939526f453c69f4c6bfe4635feb5dc5c0be */ /* qemu git hash: 805b14ffc44999952562e8f219d81c21a4fa50b9 */
/* automatically generated by rust-bindgen 0.70.1 */ /* automatically generated by rust-bindgen 0.70.1 */
pub const LIBAFL_SYNC_EXIT_OPCODE: u32 = 1727150607; pub const LIBAFL_SYNC_EXIT_OPCODE: u32 = 1727150607;

View File

@ -35,9 +35,6 @@ use libafl_bolts::{
use libafl_qemu_sys::libafl_exit_request_timeout; use libafl_qemu_sys::libafl_exit_request_timeout;
#[cfg(emulation_mode = "usermode")] #[cfg(emulation_mode = "usermode")]
use libafl_qemu_sys::libafl_qemu_handle_crash; use libafl_qemu_sys::libafl_qemu_handle_crash;
#[cfg(emulation_mode = "usermode")]
use libafl_qemu_sys::siginfo_t;
#[cfg(emulation_mode = "systemmode")]
use libc::siginfo_t; use libc::siginfo_t;
#[cfg(emulation_mode = "usermode")] #[cfg(emulation_mode = "usermode")]
@ -60,17 +57,26 @@ where
/// ///
/// This should be used as a crash handler, and nothing else. /// This should be used as a crash handler, and nothing else.
#[cfg(emulation_mode = "usermode")] #[cfg(emulation_mode = "usermode")]
unsafe fn inproc_qemu_crash_handler( unsafe fn inproc_qemu_crash_handler<ET, S>(
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
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
{
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(),
}; };
libafl_qemu_handle_crash(signal as i32, ptr::from_mut::<siginfo_t>(info), puc);
// run modules' crash callback
if let Some(emulator_modules) = EmulatorModules::<ET, S>::emulator_modules_mut() {
emulator_modules.modules_mut().on_crash_all();
}
libafl_qemu_handle_crash(signal as i32, info, puc);
} }
#[cfg(emulation_mode = "systemmode")] #[cfg(emulation_mode = "systemmode")]
@ -79,8 +85,7 @@ pub(crate) static BREAK_ON_TMOUT: AtomicBool = AtomicBool::new(false);
/// # Safety /// # Safety
/// Can call through the `unix_signal_handler::inproc_timeout_handler`. /// Can call through the `unix_signal_handler::inproc_timeout_handler`.
/// Calling this method multiple times concurrently can lead to race conditions. /// Calling this method multiple times concurrently can lead to race conditions.
#[cfg(emulation_mode = "systemmode")] pub unsafe fn inproc_qemu_timeout_handler<E, EM, ET, OF, S, Z>(
pub unsafe fn inproc_qemu_timeout_handler<E, EM, OF, Z>(
signal: Signal, signal: Signal,
info: &mut siginfo_t, info: &mut siginfo_t,
context: Option<&mut ucontext_t>, context: Option<&mut ucontext_t>,
@ -88,16 +93,36 @@ pub unsafe fn inproc_qemu_timeout_handler<E, EM, OF, Z>(
) where ) where
E: HasObservers + HasInProcessHooks<E::State> + Executor<EM, Z>, E: HasObservers + HasInProcessHooks<E::State> + Executor<EM, Z>,
E::Observers: ObserversTuple<E::Input, E::State>, E::Observers: ObserversTuple<E::Input, E::State>,
EM: EventFirer<State = E::State> + EventRestarter<State = E::State>,
OF: Feedback<EM, E::Input, E::Observers, E::State>,
E::State: HasExecutions + HasSolutions + HasCorpus, E::State: HasExecutions + HasSolutions + HasCorpus,
EM: EventFirer<State = E::State> + EventRestarter<State = E::State>,
ET: EmulatorModuleTuple<S>,
OF: Feedback<EM, E::Input, E::Observers, E::State>,
S: State + Unpin,
Z: HasObjective<Objective = OF, State = E::State>, Z: HasObjective<Objective = OF, State = E::State>,
<<E as UsesState>::State as HasSolutions>::Solutions: Corpus<Input = E::Input>, //delete me <<E as UsesState>::State as HasSolutions>::Solutions: Corpus<Input = E::Input>, //delete me
<<<E as UsesState>::State as HasCorpus>::Corpus as Corpus>::Input: Clone, //delete me <<<E as UsesState>::State as HasCorpus>::Corpus as Corpus>::Input: Clone, //delete me
{ {
#[cfg(emulation_mode = "systemmode")]
{
if BREAK_ON_TMOUT.load(Ordering::Acquire) { if BREAK_ON_TMOUT.load(Ordering::Acquire) {
libafl_exit_request_timeout(); libafl_exit_request_timeout();
} else { } else {
libafl::executors::hooks::unix::unix_signal_handler::inproc_timeout_handler::<
E,
EM,
OF,
Z,
>(signal, info, context, data);
}
}
#[cfg(emulation_mode = "usermode")]
{
// run modules' crash callback
if let Some(emulator_modules) = EmulatorModules::<ET, S>::emulator_modules_mut() {
emulator_modules.modules_mut().on_timeout_all();
}
libafl::executors::hooks::unix::unix_signal_handler::inproc_timeout_handler::<E, EM, OF, Z>( libafl::executors::hooks::unix::unix_signal_handler::inproc_timeout_handler::<E, EM, OF, Z>(
signal, info, context, data, signal, info, context, data,
); );
@ -153,7 +178,8 @@ where
#[cfg(emulation_mode = "usermode")] #[cfg(emulation_mode = "usermode")]
{ {
inner.inprocess_hooks_mut().crash_handler = inproc_qemu_crash_handler as *const c_void; inner.inprocess_hooks_mut().crash_handler =
inproc_qemu_crash_handler::<ET, S> as *const c_void;
let handler = |emulator_modules: &mut EmulatorModules<ET, S>, host_sig| { let handler = |emulator_modules: &mut EmulatorModules<ET, S>, host_sig| {
eprintln!("Crashed with signal {host_sig}"); eprintln!("Crashed with signal {host_sig}");
@ -175,15 +201,14 @@ where
} }
} }
#[cfg(emulation_mode = "systemmode")]
{
inner.inprocess_hooks_mut().timeout_handler = inproc_qemu_timeout_handler::< inner.inprocess_hooks_mut().timeout_handler = inproc_qemu_timeout_handler::<
StatefulInProcessExecutor<'a, H, OT, S, Emulator<CM, ED, ET, S, SM>>, StatefulInProcessExecutor<'a, H, OT, S, Emulator<CM, ED, ET, S, SM>>,
EM, EM,
ET,
OF, OF,
S,
Z, Z,
> as *const c_void; > as *const c_void;
}
Ok(Self { Ok(Self {
inner, inner,

View File

@ -108,6 +108,7 @@ pub struct DrCovModule<F> {
full_trace: bool, full_trace: bool,
drcov_len: usize, drcov_len: usize,
} }
impl DrCovModule<NopAddressFilter> { impl DrCovModule<NopAddressFilter> {
#[must_use] #[must_use]
pub fn builder() -> DrCovModuleBuilder<NopAddressFilter> { pub fn builder() -> DrCovModuleBuilder<NopAddressFilter> {
@ -119,11 +120,7 @@ impl DrCovModule<NopAddressFilter> {
} }
} }
} }
impl<F> DrCovModule<F> {
impl<F> DrCovModule<F>
where
F: AddressFilter,
{
#[must_use] #[must_use]
#[allow(clippy::let_underscore_untyped)] #[allow(clippy::let_underscore_untyped)]
pub fn new( pub fn new(
@ -146,84 +143,7 @@ where
} }
} }
#[must_use] pub fn write(&mut self) {
pub fn must_instrument(&self, addr: GuestAddr) -> bool {
self.filter.allowed(&addr)
}
}
impl<F, S> EmulatorModule<S> for DrCovModule<F>
where
F: AddressFilter,
S: Unpin + UsesInput + HasMetadata,
{
type ModuleAddressFilter = F;
#[cfg(emulation_mode = "systemmode")]
type ModulePageFilter = NopPageFilter;
fn init_module<ET>(&self, emulator_modules: &mut EmulatorModules<ET, S>)
where
ET: EmulatorModuleTuple<S>,
{
emulator_modules.blocks(
Hook::Function(gen_unique_block_ids::<ET, F, S>),
Hook::Function(gen_block_lengths::<ET, F, S>),
Hook::Function(exec_trace_block::<ET, F, S>),
);
}
#[cfg(emulation_mode = "usermode")]
fn first_exec<ET>(&mut self, emulator_modules: &mut EmulatorModules<ET, S>, _state: &mut S)
where
ET: EmulatorModuleTuple<S>,
{
if self.module_mapping.is_none() {
log::info!("Auto-filling module mapping for DrCov module from QEMU mapping.");
let qemu = emulator_modules.qemu();
let mut module_mapping: RangeMap<usize, (u16, String)> = RangeMap::new();
for (i, (r, p)) in qemu
.mappings()
.filter_map(|m| {
m.path()
.map(|p| ((m.start() as usize)..(m.end() as usize), p.to_string()))
.filter(|(_, p)| !p.is_empty())
})
.enumerate()
{
module_mapping.insert(r, (i as u16, p));
}
self.module_mapping = Some(module_mapping);
} else {
log::info!("Using user-provided module mapping for DrCov module.");
}
}
#[cfg(emulation_mode = "systemmode")]
fn first_exec<ET>(&mut self, _emulator_modules: &mut EmulatorModules<ET, S>, _state: &mut S)
where
ET: EmulatorModuleTuple<S>,
{
assert!(
self.module_mapping.is_some(),
"DrCov should have a module mapping already set."
);
}
fn post_exec<OT, ET>(
&mut self,
_emulator_modules: &mut EmulatorModules<ET, S>,
_state: &mut S,
_input: &S::Input,
_observers: &mut OT,
_exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S::Input, S>,
ET: EmulatorModuleTuple<S>,
{
let lengths_opt = DRCOV_LENGTHS.lock().unwrap(); let lengths_opt = DRCOV_LENGTHS.lock().unwrap();
let lengths = lengths_opt.as_ref().unwrap(); let lengths = lengths_opt.as_ref().unwrap();
if self.full_trace { if self.full_trace {
@ -322,6 +242,100 @@ where
self.drcov_len = DRCOV_MAP.lock().unwrap().as_ref().unwrap().len(); self.drcov_len = DRCOV_MAP.lock().unwrap().as_ref().unwrap().len();
} }
} }
}
impl<F> DrCovModule<F>
where
F: AddressFilter,
{
#[must_use]
pub fn must_instrument(&self, addr: GuestAddr) -> bool {
self.filter.allowed(&addr)
}
}
impl<F, S> EmulatorModule<S> for DrCovModule<F>
where
F: AddressFilter,
S: Unpin + UsesInput + HasMetadata,
{
type ModuleAddressFilter = F;
#[cfg(emulation_mode = "systemmode")]
type ModulePageFilter = NopPageFilter;
fn init_module<ET>(&self, emulator_modules: &mut EmulatorModules<ET, S>)
where
ET: EmulatorModuleTuple<S>,
{
emulator_modules.blocks(
Hook::Function(gen_unique_block_ids::<ET, F, S>),
Hook::Function(gen_block_lengths::<ET, F, S>),
Hook::Function(exec_trace_block::<ET, F, S>),
);
}
#[cfg(emulation_mode = "usermode")]
fn first_exec<ET>(&mut self, emulator_modules: &mut EmulatorModules<ET, S>, _state: &mut S)
where
ET: EmulatorModuleTuple<S>,
{
if self.module_mapping.is_none() {
log::info!("Auto-filling module mapping for DrCov module from QEMU mapping.");
let qemu = emulator_modules.qemu();
let mut module_mapping: RangeMap<usize, (u16, String)> = RangeMap::new();
for (i, (r, p)) in qemu
.mappings()
.filter_map(|m| {
m.path()
.map(|p| ((m.start() as usize)..(m.end() as usize), p.to_string()))
.filter(|(_, p)| !p.is_empty())
})
.enumerate()
{
module_mapping.insert(r, (i as u16, p));
}
self.module_mapping = Some(module_mapping);
} else {
log::info!("Using user-provided module mapping for DrCov module.");
}
}
#[cfg(emulation_mode = "systemmode")]
fn first_exec<ET>(&mut self, _emulator_modules: &mut EmulatorModules<ET, S>, _state: &mut S)
where
ET: EmulatorModuleTuple<S>,
{
assert!(
self.module_mapping.is_some(),
"DrCov should have a module mapping already set."
);
}
fn post_exec<OT, ET>(
&mut self,
_emulator_modules: &mut EmulatorModules<ET, S>,
_state: &mut S,
_input: &S::Input,
_observers: &mut OT,
_exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S::Input, S>,
ET: EmulatorModuleTuple<S>,
{
self.write();
}
unsafe fn on_crash(&mut self) {
self.write();
}
unsafe fn on_timeout(&mut self) {
self.write();
}
fn address_filter(&self) -> &Self::ModuleAddressFilter { fn address_filter(&self) -> &Self::ModuleAddressFilter {
&self.filter &self.filter

View File

@ -95,6 +95,16 @@ where
{ {
} }
/// # Safety
///
/// This is getting executed in a signal handler.
unsafe fn on_crash(&mut self) {}
/// # Safety
///
/// This is getting executed in a signal handler.
unsafe fn on_timeout(&mut self) {}
fn address_filter(&self) -> &Self::ModuleAddressFilter; fn address_filter(&self) -> &Self::ModuleAddressFilter;
fn address_filter_mut(&mut self) -> &mut Self::ModuleAddressFilter; fn address_filter_mut(&mut self) -> &mut Self::ModuleAddressFilter;
fn update_address_filter(&mut self, qemu: Qemu, filter: Self::ModuleAddressFilter) { fn update_address_filter(&mut self, qemu: Qemu, filter: Self::ModuleAddressFilter) {
@ -149,6 +159,16 @@ where
OT: ObserversTuple<S::Input, S>, OT: ObserversTuple<S::Input, S>,
ET: EmulatorModuleTuple<S>; ET: EmulatorModuleTuple<S>;
/// # Safety
///
/// This is getting executed in a signal handler.
unsafe fn on_crash_all(&mut self);
/// # Safety
///
/// This is getting executed in a signal handler.
unsafe fn on_timeout_all(&mut self);
fn allow_address_range_all(&mut self, address_range: Range<GuestAddr>); fn allow_address_range_all(&mut self, address_range: Range<GuestAddr>);
#[cfg(emulation_mode = "systemmode")] #[cfg(emulation_mode = "systemmode")]
@ -196,6 +216,10 @@ where
{ {
} }
unsafe fn on_crash_all(&mut self) {}
unsafe fn on_timeout_all(&mut self) {}
fn allow_address_range_all(&mut self, _address_range: Range<GuestAddr>) {} fn allow_address_range_all(&mut self, _address_range: Range<GuestAddr>) {}
#[cfg(emulation_mode = "systemmode")] #[cfg(emulation_mode = "systemmode")]
@ -255,6 +279,16 @@ where
.post_exec_all(emulator_modules, state, input, observers, exit_kind); .post_exec_all(emulator_modules, state, input, observers, exit_kind);
} }
unsafe fn on_crash_all(&mut self) {
self.0.on_crash();
self.1.on_crash_all();
}
unsafe fn on_timeout_all(&mut self) {
self.0.on_timeout();
self.1.on_timeout_all();
}
fn allow_address_range_all(&mut self, address_range: Range<GuestAddr>) { fn allow_address_range_all(&mut self, address_range: Range<GuestAddr>) {
self.0.address_filter_mut().register(address_range.clone()); self.0.address_filter_mut().register(address_range.clone());
self.1.allow_address_range_all(address_range); self.1.allow_address_range_all(address_range);