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 <fabian.freyer@physik.tu-berlin.de>
This commit is contained in:
Dominik Maier 2022-11-15 18:27:48 +01:00 committed by GitHub
parent e5aaf85d3c
commit 2011ed299b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 284 additions and 1 deletions

View File

@ -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"

View File

@ -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 = "*"

View File

@ -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;

View File

@ -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<Option<pthread_introspection_hook_t>>);
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<Option<PthreadIntrospectionHook>> = 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<dyn Fn(EventType, libc::pthread_t, *const libc::c_void, libc::size_t) + Sync + Send>;
/// 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<libc::c_uint> for EventType {
type Error = ();
fn try_from(value: libc::c_uint) -> Result<Self, Self::Error> {
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<EventType> 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<H>(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<Mutex<bool>> = 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<Mutex<bool>> = 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<Mutex<bool>> = 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<Mutex<bool>> = 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());
}
}