From 2011ed299b062aeb1975bcb114d9a3589f11479b Mon Sep 17 00:00:00 2001 From: Dominik Maier Date: Tue, 15 Nov 2022 18:27:48 +0100 Subject: [PATCH] Pthread introspection hook (extends #263) (#891) * Add pthread_introspection_hook support on macos See-also: #68 * Remove lazy_static * all of apple are created equal Co-authored-by: Fabian Freyer --- fuzzers/frida_libpng/Cargo.toml | 1 - libafl_frida/Cargo.toml | 3 + libafl_frida/src/lib.rs | 4 + libafl_frida/src/pthread_hook.rs | 277 +++++++++++++++++++++++++++++++ 4 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 libafl_frida/src/pthread_hook.rs diff --git a/fuzzers/frida_libpng/Cargo.toml b/fuzzers/frida_libpng/Cargo.toml index 306c8a7b41..d3cb3de5a4 100644 --- a/fuzzers/frida_libpng/Cargo.toml +++ b/fuzzers/frida_libpng/Cargo.toml @@ -31,7 +31,6 @@ capstone = "0.11.0" frida-gum = { version = "0.8.1", features = [ "auto-download", "event-sink", "invocation-listener"] } libafl_frida = { path = "../../libafl_frida", features = ["cmplog"] } libafl_targets = { path = "../../libafl_targets", features = ["sancov_cmplog"] } -lazy_static = "1.4.0" libc = "0.2" libloading = "0.7" num-traits = "0.2" diff --git a/libafl_frida/Cargo.toml b/libafl_frida/Cargo.toml index 7c6c2a6eaa..36c6679160 100644 --- a/libafl_frida/Cargo.toml +++ b/libafl_frida/Cargo.toml @@ -39,3 +39,6 @@ backtrace = { version = "0.3", default-features = false, features = ["std", "ser num-traits = "0.2" ahash = "0.7" paste = "1.0" + +[dev-dependencies] +serial_test = "*" diff --git a/libafl_frida/src/lib.rs b/libafl_frida/src/lib.rs index 47ad61d76a..4078d91472 100644 --- a/libafl_frida/src/lib.rs +++ b/libafl_frida/src/lib.rs @@ -74,6 +74,10 @@ pub mod windows_hooks; pub mod coverage_rt; +/// Hooking thread lifecycle events. Seems like this is apple-only for now. +#[cfg(any(target_vendor = "apple"))] +pub mod pthread_hook; + #[cfg(feature = "cmplog")] /// The frida cmplog runtime pub mod cmplog_rt; diff --git a/libafl_frida/src/pthread_hook.rs b/libafl_frida/src/pthread_hook.rs new file mode 100644 index 0000000000..b783aeb5c5 --- /dev/null +++ b/libafl_frida/src/pthread_hook.rs @@ -0,0 +1,277 @@ +use std::{ + cell::UnsafeCell, + convert::{TryFrom, TryInto}, + sync::RwLock, +}; + +/// Rust bindings for Apple's [`pthread_introspection`](https://opensource.apple.com/source/libpthread/libpthread-218.20.1/pthread/introspection.h.auto.html) hooks. +use libc; + +const PTHREAD_INTROSPECTION_THREAD_CREATE: libc::c_uint = 1; +const PTHREAD_INTROSPECTION_THREAD_START: libc::c_uint = 2; +const PTHREAD_INTROSPECTION_THREAD_TERMINATE: libc::c_uint = 3; +const PTHREAD_INTROSPECTION_THREAD_DESTROY: libc::c_uint = 4; + +#[allow(non_camel_case_types)] +type pthread_introspection_hook_t = extern "C" fn( + event: libc::c_uint, + thread: libc::pthread_t, + addr: *const libc::c_void, + size: libc::size_t, +); + +extern "C" { + fn pthread_introspection_hook_install( + hook: *const libc::c_void, + ) -> pthread_introspection_hook_t; +} + +struct PreviousHook(UnsafeCell>); + +impl PreviousHook { + /// Dispatch to the previous hook, if it is set. + pub fn dispatch( + &self, + event: libc::c_uint, + thread: libc::pthread_t, + addr: *const libc::c_void, + size: libc::size_t, + ) { + let inner = unsafe { *self.0.get() }; + if inner.is_none() { + return; + } + let inner = inner.unwrap(); + inner(event, thread, addr, size); + } + + /// Set the previous hook. + pub fn set(&self, hook: pthread_introspection_hook_t) { + unsafe { + *self.0.get() = Some(hook); + } + } + + /// Ensure the previous hook is installed again. + pub fn reset(&self) { + let inner = unsafe { *self.0.get() }; + if inner.is_none() { + unsafe { + pthread_introspection_hook_install(std::ptr::null()); + } + return; + } + let inner = inner.unwrap(); + unsafe { + *self.0.get() = None; + pthread_introspection_hook_install(inner as *const libc::c_void); + } + } +} + +// At the time where the inner is called, it will have been set. +// Mark it as sync. +unsafe impl Sync for PreviousHook {} + +#[allow(non_upper_case_globals)] +static PREVIOUS_HOOK: PreviousHook = PreviousHook(UnsafeCell::new(None)); + +static CURRENT_HOOK: RwLock> = RwLock::new(None); + +extern "C" fn pthread_introspection_hook( + event: libc::c_uint, + thread: libc::pthread_t, + addr: *const libc::c_void, + size: libc::size_t, +) { + if let Some(ref hook) = *CURRENT_HOOK.read().unwrap() { + hook(event.try_into().unwrap(), thread, addr, size); + } + PREVIOUS_HOOK.dispatch(event, thread, addr, size); +} + +/// Closure type for `pthread_introspection` hooks. +pub type PthreadIntrospectionHook = + Box; + +/// Event type describing the lifecycle of a pthread. +#[derive(Debug, PartialEq, Eq)] +pub enum EventType { + /// `pthread` creation + Create, + /// `pthread` starts + Start, + /// `pthread` terminates + Terminate, + /// `pthread` is being destroyed + Destroy, +} + +impl TryFrom for EventType { + type Error = (); + + fn try_from(value: libc::c_uint) -> Result { + match value { + PTHREAD_INTROSPECTION_THREAD_CREATE => Ok(Self::Create), + PTHREAD_INTROSPECTION_THREAD_START => Ok(Self::Start), + PTHREAD_INTROSPECTION_THREAD_TERMINATE => Ok(Self::Terminate), + PTHREAD_INTROSPECTION_THREAD_DESTROY => Ok(Self::Destroy), + _ => Err(()), + } + } +} + +impl From for libc::c_uint { + fn from(event: EventType) -> Self { + match event { + EventType::Create => PTHREAD_INTROSPECTION_THREAD_CREATE, + EventType::Start => PTHREAD_INTROSPECTION_THREAD_START, + EventType::Terminate => PTHREAD_INTROSPECTION_THREAD_TERMINATE, + EventType::Destroy => PTHREAD_INTROSPECTION_THREAD_DESTROY, + } + } +} + +/// Set a `pthread_introspection` hook. +/// # Example +/// ``` +///# use libafl_frida::pthread_hook; +///# use std::time::Duration; +///# use std::thread; +/// pthread_hook::install(|event, pthread, addr, size| { +/// println!("thread id=0x{:x} event={:?} addr={:?} size={:x}", pthread, event, addr, size); +/// }); +///# thread::spawn(|| { +///# thread::sleep(Duration::from_millis(1)); +///# }); +///# thread::sleep(Duration::from_millis(50)); +/// ``` +/// This should output the thread IDs, lifecycle events, addresses and sizes of the corresponding events. +/// ```no_test +/// thread id=0x16bf67000 event=Create addr=0x16bf67000 size=4000 +/// thread id=0x16bf67000 event=Start addr=0x16bd60000 size=208000 +/// thread id=0x16bf67000 event=Terminate addr=0x16bd60000 size=208000 +/// thread id=0x16bf67000 event=Destroy addr=0x16bf67000 size=4000 +/// ``` +pub fn install(hook: H) +where + H: Fn(EventType, libc::pthread_t, *const libc::c_void, libc::size_t) + Send + Sync + 'static, +{ + let mut new_hook = CURRENT_HOOK.write().unwrap(); + *new_hook = Some(Box::new(hook)); + + let prev = unsafe { + pthread_introspection_hook_install(pthread_introspection_hook as *const libc::c_void) + }; + + // Allow because we're sure this isn't from a different code generation unit. + #[allow(clippy::fn_address_comparisons)] + if !(prev as *const libc::c_void).is_null() && prev != pthread_introspection_hook { + PREVIOUS_HOOK.set(prev); + } +} + +/// Restore a previously set `pthread_introspection` hook. +/// # Example +/// ``` +///# use libafl_frida::pthread_hook; +///# use std::time::Duration; +///# use std::thread; +/// pthread_hook::reset(); +/// ``` +pub fn reset() { + PREVIOUS_HOOK.reset(); +} + +/// The following tests fail if they are not run sequentially. +#[cfg(test)] +mod test { + use std::{ + sync::{Arc, Mutex}, + thread, + time::Duration, + }; + + use serial_test::serial; + + #[test] + #[serial] + fn test_nohook_thread_create() { + let triggered: Arc> = Arc::new(Mutex::new(false)); + + thread::spawn(|| { + thread::sleep(Duration::from_millis(1)); + }); + thread::sleep(Duration::from_millis(50)); + + super::reset(); + assert!(!*triggered.lock().unwrap()); + } + + #[test] + #[serial] + fn test_hook_thread_create() { + let triggered: Arc> = Arc::new(Mutex::new(false)); + + let inner_triggered = triggered.clone(); + super::install(move |event, _, _, _| { + if event == super::EventType::Create { + let mut triggered = inner_triggered.lock().unwrap(); + *triggered = true; + } + }); + + thread::spawn(|| { + thread::sleep(Duration::from_millis(1)); + }); + thread::sleep(Duration::from_millis(50)); + + super::reset(); + assert!(*triggered.lock().unwrap()); + } + + #[test] + #[serial] + fn test_hook_thread_start() { + let triggered: Arc> = Arc::new(Mutex::new(false)); + + let inner_triggered = triggered.clone(); + super::install(move |event, _, _, _| { + if event == super::EventType::Start { + let mut triggered = inner_triggered.lock().unwrap(); + *triggered = true; + } + }); + + thread::spawn(|| { + thread::sleep(Duration::from_millis(1)); + }); + thread::sleep(Duration::from_millis(50)); + + super::reset(); + assert!(*triggered.lock().unwrap()); + } + + #[test] + #[serial] + fn test_hook_reset() { + let triggered: Arc> = Arc::new(Mutex::new(false)); + + let inner_triggered = triggered.clone(); + super::install(move |event, _, _, _| { + if event == super::EventType::Start { + let mut triggered = inner_triggered.lock().unwrap(); + *triggered = true; + } + }); + + super::reset(); + + thread::spawn(|| { + thread::sleep(Duration::from_millis(1)); + }); + thread::sleep(Duration::from_millis(50)); + + assert!(!*triggered.lock().unwrap()); + } +}