From bf3b63a0da2fb862f532a4727bc22ed02fd0187a Mon Sep 17 00:00:00 2001 From: Andrew Werner Date: Wed, 11 Sep 2024 19:06:11 -0400 Subject: [PATCH] frida: clarify runtime lifetimes Also introduces an abstraction around GObject to try to more appropriately capture the situation. --- frida/src/device.rs | 72 ++++++++++-------------------- frida/src/device_manager.rs | 41 ++++++++--------- frida/src/injector.rs | 40 ++++++++--------- frida/src/lib.rs | 89 ++++++++++++++++++++++++++++++++++--- frida/src/process.rs | 74 ++++++++++-------------------- 5 files changed, 165 insertions(+), 151 deletions(-) diff --git a/frida/src/device.rs b/frida/src/device.rs index a6385e30..66073607 100644 --- a/frida/src/device.rs +++ b/frida/src/device.rs @@ -7,37 +7,35 @@ use frida_sys::_FridaDevice; use std::collections::HashMap; use std::ffi::{CStr, CString}; -use std::marker::PhantomData; use crate::process::Process; use crate::session::Session; use crate::variant::Variant; -use crate::{Error, Result, SpawnOptions}; +use crate::{Error, Frida, GObject, Result, SpawnOptions}; /// Access to a Frida device. -pub struct Device<'a> { - pub(crate) device_ptr: *mut _FridaDevice, - phantom: PhantomData<&'a _FridaDevice>, +pub struct Device(GObject<_FridaDevice>); + +impl Device { + pub(crate) fn ptr(&self) -> *mut _FridaDevice { + self.0.ptr() + } } -impl<'a> Device<'a> { - pub(crate) fn from_raw(device_ptr: *mut _FridaDevice) -> Device<'a> { - Device { - device_ptr, - phantom: PhantomData, - } +impl Device { + pub(crate) fn from_raw(frida: Frida, ptr: *mut _FridaDevice) -> Self { + Self(GObject::new(ptr, frida)) } /// Returns the device's name. pub fn get_name(&self) -> &str { - let name = - unsafe { CStr::from_ptr(frida_sys::frida_device_get_name(self.device_ptr) as _) }; + let name = unsafe { CStr::from_ptr(frida_sys::frida_device_get_name(self.0.ptr()) as _) }; name.to_str().unwrap_or_default() } /// Returns the device's id. pub fn get_id(&self) -> &str { - let id = unsafe { CStr::from_ptr(frida_sys::frida_device_get_id(self.device_ptr) as _) }; + let id = unsafe { CStr::from_ptr(frida_sys::frida_device_get_id(self.0.ptr()) as _) }; id.to_str().unwrap_or_default() } @@ -52,7 +50,7 @@ impl<'a> Device<'a> { /// assert_eq!(device.get_type(), DeviceType::Local); /// ``` pub fn get_type(&self) -> DeviceType { - unsafe { frida_sys::frida_device_get_dtype(self.device_ptr).into() } + unsafe { frida_sys::frida_device_get_dtype(self.0.ptr()).into() } } /// Returns the device's system parameters @@ -79,7 +77,7 @@ impl<'a> Device<'a> { let ht = unsafe { frida_sys::frida_device_query_system_parameters_sync( - self.device_ptr, + self.0.ptr(), std::ptr::null_mut(), &mut error, ) @@ -115,20 +113,17 @@ impl<'a> Device<'a> { /// Returns if the device is lost or not. pub fn is_lost(&self) -> bool { - unsafe { frida_sys::frida_device_is_lost(self.device_ptr) == 1 } + unsafe { frida_sys::frida_device_is_lost(self.0.ptr()) == 1 } } /// Returns all processes. - pub fn enumerate_processes<'b>(&'a self) -> Vec> - where - 'a: 'b, - { + pub fn enumerate_processes(&self) -> Vec { let mut processes = Vec::new(); let mut error: *mut frida_sys::GError = std::ptr::null_mut(); let processes_ptr = unsafe { frida_sys::frida_device_enumerate_processes_sync( - self.device_ptr, + self.0.ptr(), std::ptr::null_mut(), std::ptr::null_mut(), &mut error, @@ -141,7 +136,7 @@ impl<'a> Device<'a> { for i in 0..num_processes { let process_ptr = unsafe { frida_sys::frida_process_list_get(processes_ptr, i) }; - let process = Process::from_raw(process_ptr); + let process = Process::from_raw(self.0.runtime().clone(), process_ptr); processes.push(process); } } @@ -151,14 +146,11 @@ impl<'a> Device<'a> { } /// Creates [`Session`] and attaches the device to the current PID. - pub fn attach<'b>(&'a self, pid: u32) -> Result> - where - 'a: 'b, - { + pub fn attach(&self, pid: u32) -> Result { let mut error: *mut frida_sys::GError = std::ptr::null_mut(); let session = unsafe { frida_sys::frida_device_attach_sync( - self.device_ptr, + self.ptr(), pid, std::ptr::null_mut(), std::ptr::null_mut(), @@ -184,9 +176,9 @@ impl<'a> Device<'a> { let pid = unsafe { frida_sys::frida_device_spawn_sync( - self.device_ptr, + self.ptr(), program.as_ptr(), - options.options_ptr, + options.ptr(), std::ptr::null_mut(), &mut error, ) @@ -208,12 +200,7 @@ impl<'a> Device<'a> { pub fn resume(&self, pid: u32) -> Result<()> { let mut error: *mut frida_sys::GError = std::ptr::null_mut(); unsafe { - frida_sys::frida_device_resume_sync( - self.device_ptr, - pid, - std::ptr::null_mut(), - &mut error, - ) + frida_sys::frida_device_resume_sync(self.ptr(), pid, std::ptr::null_mut(), &mut error) }; if !error.is_null() { @@ -232,12 +219,7 @@ impl<'a> Device<'a> { pub fn kill(&mut self, pid: u32) -> Result<()> { let mut error: *mut frida_sys::GError = std::ptr::null_mut(); unsafe { - frida_sys::frida_device_kill_sync( - self.device_ptr, - pid, - std::ptr::null_mut(), - &mut error, - ) + frida_sys::frida_device_kill_sync(self.ptr(), pid, std::ptr::null_mut(), &mut error) }; if !error.is_null() { @@ -253,12 +235,6 @@ impl<'a> Device<'a> { } } -impl<'a> Drop for Device<'a> { - fn drop(&mut self) { - unsafe { frida_sys::frida_unref(self.device_ptr as _) } - } -} - #[repr(u32)] #[non_exhaustive] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] diff --git a/frida/src/device_manager.rs b/frida/src/device_manager.rs index e57ab111..f3e703b7 100644 --- a/frida/src/device_manager.rs +++ b/frida/src/device_manager.rs @@ -6,7 +6,6 @@ use frida_sys::_FridaDeviceManager; use std::ffi::CString; -use std::marker::PhantomData; use crate::device::{self, Device}; use crate::DeviceType; @@ -15,29 +14,24 @@ use crate::Frida; use crate::Result; /// Platform-independent device manager abstraction access. -pub struct DeviceManager<'a> { +#[derive(Clone)] +pub struct DeviceManager { + frida: Frida, manager_ptr: *mut _FridaDeviceManager, - phantom: PhantomData<&'a _FridaDeviceManager>, } -impl<'a> DeviceManager<'a> { +impl DeviceManager { /// Obtain an DeviceManager handle, ensuring that the runtime is properly initialized. This may be called as many /// times as needed, and results in a no-op if the DeviceManager is already initialized. - pub fn obtain<'b>(_frida: &'b Frida) -> Self - where - 'b: 'a, - { + pub fn obtain(frida: &Frida) -> Self { DeviceManager { manager_ptr: unsafe { frida_sys::frida_device_manager_new() }, - phantom: PhantomData, + frida: frida.clone(), } } /// Returns all devices. - pub fn enumerate_all_devices<'b>(&'a self) -> Vec> - where - 'a: 'b, - { + pub fn enumerate_all_devices(&self) -> Vec { let mut devices = Vec::new(); let mut error: *mut frida_sys::GError = std::ptr::null_mut(); @@ -54,8 +48,9 @@ impl<'a> DeviceManager<'a> { devices.reserve(num_devices as usize); for i in 0..num_devices { - let device = - Device::from_raw(unsafe { frida_sys::frida_device_list_get(devices_ptr, i) }); + let device = Device::from_raw(self.frida.clone(), unsafe { + frida_sys::frida_device_list_get(devices_ptr, i) + }); devices.push(device); } } @@ -65,7 +60,7 @@ impl<'a> DeviceManager<'a> { } /// Returns the device of the specified type. - pub fn get_device_by_type(&'a self, r#type: DeviceType) -> Result> { + pub fn get_device_by_type(&self, r#type: DeviceType) -> Result { let mut error: *mut frida_sys::GError = std::ptr::null_mut(); let device_ptr = unsafe { @@ -82,11 +77,11 @@ impl<'a> DeviceManager<'a> { return Err(Error::DeviceLookupFailed); } - return Ok(Device::from_raw(device_ptr)); + return Ok(Device::from_raw(self.frida.clone(), device_ptr)); } /// Returns the remote device with the specified host. - pub fn get_remote_device(&'a self, host: &str) -> Result> { + pub fn get_remote_device(self, host: &str) -> Result { let mut error: *mut frida_sys::GError = std::ptr::null_mut(); let host_cstring = CString::new(host).map_err(|_| Error::CStringFailed)?; @@ -104,11 +99,11 @@ impl<'a> DeviceManager<'a> { return Err(Error::DeviceLookupFailed); } - return Ok(Device::from_raw(device_ptr)); + return Ok(Device::from_raw(self.frida.clone(), device_ptr)); } /// Returns the local device. - pub fn get_local_device(&'a self) -> Result> { + pub fn get_local_device(&self) -> Result { self.get_device_by_type(device::DeviceType::Local) } @@ -123,7 +118,7 @@ impl<'a> DeviceManager<'a> { /// let device = device_manager.get_device_by_id(id).unwrap(); /// assert_eq!(device.get_id(), id); /// - pub fn get_device_by_id(&'a self, device_id: &str) -> Result> { + pub fn get_device_by_id(&self, device_id: &str) -> Result { let mut error: *mut frida_sys::GError = std::ptr::null_mut(); let cstring = CString::new(device_id).unwrap(); @@ -141,11 +136,11 @@ impl<'a> DeviceManager<'a> { return Err(Error::DeviceLookupFailed); } - return Ok(Device::from_raw(device_ptr)); + return Ok(Device::from_raw(self.frida.clone(), device_ptr)); } } -impl<'a> Drop for DeviceManager<'a> { +impl Drop for DeviceManager { fn drop(&mut self) { unsafe { frida_sys::frida_unref(self.manager_ptr as _) } } diff --git a/frida/src/injector.rs b/frida/src/injector.rs index 1fd7eb2a..6697412d 100644 --- a/frida/src/injector.rs +++ b/frida/src/injector.rs @@ -1,6 +1,5 @@ -use crate::{Device, Error, Result}; +use crate::{Device, Error, Frida, Result}; use std::ffi::CString; -use std::marker::PhantomData; use std::path::Path; #[cfg(unix)] @@ -11,16 +10,18 @@ use frida_sys::{_FridaInjector, g_bytes_new, g_bytes_unref}; /// Local library injector /// /// Implements [Inject] to allow library injection into a target process. -pub struct Injector<'a> { +pub struct Injector { + _frida: Frida, injector_ptr: *mut _FridaInjector, - phantom: PhantomData<&'a _FridaInjector>, } -impl<'a> Injector<'a> { - pub(crate) fn from_raw(injector_ptr: *mut _FridaInjector) -> Injector<'a> { +unsafe impl Send for Injector {} + +impl Injector { + pub(crate) fn from_raw(frida: Frida, injector_ptr: *mut _FridaInjector) -> Injector { Injector { + _frida: frida, injector_ptr, - phantom: PhantomData, } } @@ -28,8 +29,8 @@ impl<'a> Injector<'a> { /// /// The `frida-helper` is a binary compiled into the Frida devkit, and is codesigned /// to allow debugging. It is spawned and injection is delegated to the helper. - pub fn new() -> Self { - Self::from_raw(unsafe { frida_sys::frida_injector_new() }) + pub fn new(frida: Frida) -> Self { + Self::from_raw(frida, unsafe { frida_sys::frida_injector_new() }) } /// Create a new inprocess Injector @@ -39,19 +40,14 @@ impl<'a> Injector<'a> { /// See [Injector::new] for details about the `frida-helper` process. Using an /// in_process injector may require the debugger process to be codesigned on some /// platforms. - pub fn in_process() -> Self { - Self::from_raw(unsafe { frida_sys::frida_injector_new_inprocess() }) - } -} - -impl<'a> Default for Injector<'a> { - fn default() -> Self { - Self::new() + pub fn in_process(frida: Frida) -> Self { + Self::from_raw(frida, unsafe { frida_sys::frida_injector_new_inprocess() }) } } -impl Drop for Injector<'_> { +impl Drop for Injector { fn drop(&mut self) { + eprintln!("dropping injector"); unsafe { frida_sys::frida_injector_close_sync( self.injector_ptr, @@ -130,7 +126,7 @@ pub trait Inject { E: AsRef; } -impl<'a> Inject for Injector<'a> { +impl<'a> Inject for Injector { fn inject_library_file_sync( &mut self, pid: u32, @@ -228,7 +224,7 @@ impl<'a> Inject for Injector<'a> { } } -impl<'a> Inject for Device<'a> { +impl Inject for Device { fn inject_library_file_sync( &mut self, pid: u32, @@ -261,7 +257,7 @@ impl<'a> Inject for Device<'a> { let id = unsafe { frida_sys::frida_device_inject_library_file_sync( - self.device_ptr, + self.ptr(), pid as frida_sys::guint, path.as_ptr() as *const frida_sys::gchar, entrypoint.as_ptr() as *const frida_sys::gchar, @@ -301,7 +297,7 @@ impl<'a> Inject for Device<'a> { let id = unsafe { let g_blob = g_bytes_new(blob.as_ptr() as _, blob.len() as _); let id = frida_sys::frida_device_inject_library_blob_sync( - self.device_ptr, + self.ptr(), pid, g_blob, entrypoint.as_ptr() as *const frida_sys::gchar, diff --git a/frida/src/lib.rs b/frida/src/lib.rs index ea851d00..dd3b205e 100644 --- a/frida/src/lib.rs +++ b/frida/src/lib.rs @@ -11,7 +11,10 @@ #![deny(missing_docs)] #![allow(clippy::missing_safety_doc)] -use std::ffi::CStr; +use std::{ + ffi::CStr, + sync::{Arc, Mutex}, +}; mod device; pub use device::*; @@ -41,15 +44,36 @@ pub use variant::*; pub type Result = std::result::Result; /// Context required for instantiation of all structures under the Frida namespace. -pub struct Frida; +#[derive(Clone)] +pub struct Frida { + inner: Option>, +} + +impl Drop for Frida { + fn drop(&mut self) { + let inner = self.inner.take().expect("frida taken more than once"); + drop(inner); + let mut singleton = THE_ONE_TRUE_FRIDA.lock().unwrap(); + let Some(v) = singleton.take_if(|v| Arc::strong_count(v) == 1) else { + return; + }; + match Arc::try_unwrap(v) { + Ok(v) => drop(v), + Err(_v) => panic!("programming error!"), + } + } +} impl Frida { /// Obtain a Frida handle, ensuring that the runtime is properly initialized. This may /// be called as many times as needed, and results in a no-op if the Frida runtime is /// already initialized. - pub unsafe fn obtain() -> Frida { - frida_sys::frida_init(); - Frida {} + pub fn obtain() -> Self { + let mut singleton = THE_ONE_TRUE_FRIDA.lock().unwrap(); + let v = singleton.get_or_insert_with(|| Arc::new(FridaSingleton::new())); + Self { + inner: Some(v.clone()), + } } /// Gets the current version of frida core @@ -96,8 +120,59 @@ impl Frida { } } -impl Drop for Frida { +// A marker type that exists only while Gum is initialized. +struct FridaSingleton; + +impl FridaSingleton { + fn new() -> Self { + unsafe { frida_sys::frida_init() }; + FridaSingleton + } +} + +impl Drop for FridaSingleton { fn drop(&mut self) { - unsafe { frida_sys::frida_deinit() }; + unsafe { frida_sys::frida_deinit() } + } +} + +static THE_ONE_TRUE_FRIDA: Mutex>> = Mutex::new(None); + +type GObject = gobject::GObject; + +mod gobject { + + pub(crate) trait Runtime: Clone { + fn unref(ptr: *mut T); + } + + impl Runtime for super::Frida { + fn unref(ptr: *mut T) { + unsafe { frida_sys::frida_unref(ptr as *mut _) } + } + } + + pub(crate) struct GObject(*mut T, RT); + + impl GObject { + pub(crate) fn ptr(&self) -> *mut T { + let &Self(ptr, _) = self; + ptr + } + + pub(crate) fn new(ptr: *mut T, runtime: RT) -> Self { + Self(ptr, runtime.clone()) + } + + pub(crate) fn runtime(&self) -> &RT { + let Self(_, rt) = self; + rt + } + } + + impl Drop for GObject { + fn drop(&mut self) { + RT::unref(self.0) + } } } diff --git a/frida/src/process.rs b/frida/src/process.rs index 2b126ce4..3aaf8cf4 100644 --- a/frida/src/process.rs +++ b/frida/src/process.rs @@ -6,39 +6,28 @@ use frida_sys::{FridaSpawnOptions, _FridaProcess}; use std::ffi::{CStr, CString}; -use std::marker::PhantomData; + +use crate::{Frida, GObject}; /// Process management in Frida. -pub struct Process<'a> { - process_ptr: *mut _FridaProcess, - phantom: PhantomData<&'a _FridaProcess>, -} +pub struct Process(GObject<_FridaProcess>); -impl<'a> Process<'a> { - pub(crate) fn from_raw(process_ptr: *mut _FridaProcess) -> Process<'a> { - Process { - process_ptr, - phantom: PhantomData, - } +impl Process { + pub(crate) fn from_raw(frida: Frida, process_ptr: *mut _FridaProcess) -> Process { + Self(GObject::new(process_ptr, frida)) } /// Returns the name of the process. pub fn get_name(&self) -> &str { let process_name = - unsafe { CStr::from_ptr(frida_sys::frida_process_get_name(self.process_ptr) as _) }; + unsafe { CStr::from_ptr(frida_sys::frida_process_get_name(self.0.ptr()) as _) }; process_name.to_str().unwrap_or_default() } /// Returns the process ID of the process. pub fn get_pid(&self) -> u32 { - unsafe { frida_sys::frida_process_get_pid(self.process_ptr) } - } -} - -impl<'a> Drop for Process<'a> { - fn drop(&mut self) { - unsafe { frida_sys::frida_unref(self.process_ptr as _) } + unsafe { frida_sys::frida_process_get_pid(self.0.ptr()) } } } @@ -54,22 +43,20 @@ pub enum SpawnStdio { } /// Process Spawn Options -pub struct SpawnOptions<'a> { - pub(crate) options_ptr: *mut FridaSpawnOptions, - phantom: PhantomData<&'a FridaSpawnOptions>, -} +pub struct SpawnOptions(GObject); -impl<'a> SpawnOptions<'a> { - pub(crate) fn from_raw(options_ptr: *mut FridaSpawnOptions) -> Self { - Self { - options_ptr, - phantom: PhantomData, - } +impl SpawnOptions { + pub(crate) fn from_raw(frida: Frida, options_ptr: *mut FridaSpawnOptions) -> Self { + Self(GObject::new(options_ptr, frida)) + } + + pub(crate) fn ptr(&self) -> *mut FridaSpawnOptions { + self.0.ptr() } /// Create an empty SpawnOptions instance - pub fn new() -> Self { - Self::from_raw(unsafe { frida_sys::frida_spawn_options_new() }) + pub fn new(frida: Frida) -> Self { + Self::from_raw(frida, unsafe { frida_sys::frida_spawn_options_new() }) } /// Set the argv vector @@ -85,7 +72,7 @@ impl<'a> SpawnOptions<'a> { let mut arg_ptrs: Vec<*mut _> = args.iter().map(|s| s.as_ptr() as *mut _).collect(); unsafe { frida_sys::frida_spawn_options_set_argv( - self.options_ptr, + self.0.ptr(), arg_ptrs.as_mut_ptr(), arg_ptrs.len().try_into().unwrap(), ); @@ -96,10 +83,7 @@ impl<'a> SpawnOptions<'a> { /// Set the working directory pub fn cwd>(self, cwd: S) -> Self { unsafe { - frida_sys::frida_spawn_options_set_cwd( - self.options_ptr, - cwd.as_ref().as_ptr() as *mut _, - ); + frida_sys::frida_spawn_options_set_cwd(self.0.ptr(), cwd.as_ref().as_ptr() as *mut _); } self } @@ -120,7 +104,7 @@ impl<'a> SpawnOptions<'a> { let mut env_ptrs: Vec<*mut _> = env.iter().map(|s| s.as_ptr() as *mut _).collect(); unsafe { frida_sys::frida_spawn_options_set_env( - self.options_ptr, + self.0.ptr(), env_ptrs.as_mut_ptr(), env_ptrs.len().try_into().unwrap(), ); @@ -144,7 +128,7 @@ impl<'a> SpawnOptions<'a> { let mut envp_ptrs: Vec<*mut _> = envp.iter().map(|s| s.as_ptr() as *mut _).collect(); unsafe { frida_sys::frida_spawn_options_set_envp( - self.options_ptr, + self.0.ptr(), envp_ptrs.as_mut_ptr(), envp_ptrs.len().try_into().unwrap(), ); @@ -154,19 +138,7 @@ impl<'a> SpawnOptions<'a> { /// Set the Standard I/O handling pub fn stdio(self, stdio: SpawnStdio) -> Self { - unsafe { frida_sys::frida_spawn_options_set_stdio(self.options_ptr, stdio as _) } + unsafe { frida_sys::frida_spawn_options_set_stdio(self.0.ptr(), stdio as _) } self } } - -impl<'a> Default for SpawnOptions<'a> { - fn default() -> Self { - Self::new() - } -} - -impl<'a> Drop for SpawnOptions<'a> { - fn drop(&mut self) { - unsafe { frida_sys::frida_unref(self.options_ptr as _) } - } -}