pub use mach2::{
kern_return::{kern_return_t, KERN_SUCCESS},
port::mach_port_name_t,
task::{self, task_threads},
task_info,
thread_act::thread_get_state,
traps::mach_task_self,
vm::{mach_vm_deallocate, mach_vm_read, mach_vm_region_recurse},
vm_region::vm_region_submap_info_64,
};
#[derive(thiserror::Error, Debug)]
pub enum KernelError {
#[error("specified address is not currently valid")]
InvalidAddress = 1,
#[error("specified memory is valid, but does not permit the required forms of access")]
ProtectionFailure = 2,
#[error("the address range specified is already in use, or no address range of the size specified could be found")]
NoSpace = 3,
#[error("the function requested was not applicable to this type of argument, or an argument is invalid")]
InvalidArgument = 4,
#[error("the function could not be performed")]
Failure = 5,
#[error("system resource could not be allocated to fulfill this request")]
ResourceShortage = 6,
#[error("the task in question does not hold receive rights for the port argument")]
NotReceiver = 7,
#[error("bogus access restriction")]
NoAccess = 8,
#[error(
"during a page fault, the target address refers to a memory object that has been destroyed"
)]
MemoryFailure = 9,
#[error(
"during a page fault, the memory object indicated that the data could not be returned"
)]
MemoryError = 10,
#[error("the receive right is already a member of the portset")]
AlreadyInSet = 11,
#[error("the receive right is not a member of a port set")]
NotInSet = 12,
#[error("the name already denotes a right in the task")]
NameExists = 13,
#[error("the operation was aborted")]
Aborted = 14,
#[error("the name doesn't denote a right in the task")]
InvalidName = 15,
#[error("target task isn't an active task")]
InvalidTask = 16,
#[error("the name denotes a right, but not an appropriate right")]
InvalidRight = 17,
#[error("a blatant range error")]
InvalidValue = 18,
#[error("operation would overflow limit on user-references")]
UserRefsOverflow = 19,
#[error("the supplied port capability is improper")]
InvalidCapability = 20,
#[error("the task already has send or receive rights for the port under another name")]
RightExists = 21,
#[error("target host isn't actually a host")]
InvalidHost = 22,
#[error("an attempt was made to supply 'precious' data for memory that is already present in a memory object")]
MemoryPresent = 23,
#[error("an argument applied to assert processor set privilege was not a processor set control port")]
InvalidProcessorSet = 26,
#[error("the specified scheduling attributes exceed the thread's limits")]
PolicyLimit = 27,
#[error("the specified scheduling policy is not currently enabled for the processor set")]
InvalidPolicy = 28,
#[error("the external memory manager failed to initialize the memory object")]
InvalidObject = 29,
#[error(
"a thread is attempting to wait for an event for which there is already a waiting thread"
)]
AlreadyWaiting = 30,
#[error("an attempt was made to destroy the default processor set")]
DefaultSet = 31,
#[error("an attempt was made to fetch an exception port that is protected, or to abort a thread while processing a protected exception")]
ExceptionProtected = 32,
#[error("a ledger was required but not supplied")]
InvalidLedger = 33,
#[error("the port was not a memory cache control port")]
InvalidMemoryControl = 34,
#[error("an argument supplied to assert security privilege was not a host security port")]
InvalidSecurity = 35,
#[error("thread_depress_abort was called on a thread which was not currently depressed")]
NotDepressed = 36,
#[error("object has been terminated and is no longer available")]
Terminated = 37,
#[error("lock set has been destroyed and is no longer available")]
LockSetDestroyed = 38,
#[error("the thread holding the lock terminated before releasing the lock")]
LockUnstable = 39,
#[error("the lock is already owned by another thread")]
LockOwned = 40,
#[error("the lock is already owned by the calling thread")]
LockOwnedSelf = 41,
#[error("semaphore has been destroyed and is no longer available")]
SemaphoreDestroyed = 42,
#[error("return from RPC indicating the target server was terminated before it successfully replied")]
RpcServerTerminated = 43,
#[error("terminate an orphaned activation")]
RpcTerminateOrphan = 44,
#[error("allow an orphaned activation to continue executing")]
RpcContinueOrphan = 45,
#[error("empty thread activation (No thread linked to it)")]
NotSupported = 46,
#[error("remote node down or inaccessible")]
NodeDown = 47,
#[error("a signalled thread was not actually waiting")]
NotWaiting = 48,
#[error("some thread-oriented operation (semaphore_wait) timed out")]
OperationTimedOut = 49,
#[error("during a page fault, indicates that the page was rejected as a result of a signature check")]
CodesignError = 50,
#[error("the requested property cannot be changed at this time")]
PoicyStatic = 51,
#[error("the provided buffer is of insufficient size for the requested data")]
InsufficientBufferSize = 52,
#[error("denied by security policy")]
Denied = 53,
#[error("the KC on which the function is operating is missing")]
MissingKC = 54,
#[error("the KC on which the function is operating is invalid")]
InvalidKC = 55,
#[error("a search or query operation did not return a result")]
NotFound = 56,
}
impl From<mach2::kern_return::kern_return_t> for KernelError {
fn from(kr: mach2::kern_return::kern_return_t) -> Self {
use mach2::kern_return::*;
match kr {
KERN_INVALID_ADDRESS => Self::InvalidAddress,
KERN_PROTECTION_FAILURE => Self::ProtectionFailure,
KERN_NO_SPACE => Self::NoSpace,
KERN_INVALID_ARGUMENT => Self::InvalidArgument,
KERN_FAILURE => Self::Failure,
KERN_RESOURCE_SHORTAGE => Self::ResourceShortage,
KERN_NOT_RECEIVER => Self::NotReceiver,
KERN_NO_ACCESS => Self::NoAccess,
KERN_MEMORY_FAILURE => Self::MemoryFailure,
KERN_MEMORY_ERROR => Self::MemoryError,
KERN_ALREADY_IN_SET => Self::AlreadyInSet,
KERN_NAME_EXISTS => Self::NameExists,
KERN_INVALID_NAME => Self::InvalidName,
KERN_INVALID_TASK => Self::InvalidTask,
KERN_INVALID_RIGHT => Self::InvalidRight,
KERN_INVALID_VALUE => Self::InvalidValue,
KERN_UREFS_OVERFLOW => Self::UserRefsOverflow,
KERN_INVALID_CAPABILITY => Self::InvalidCapability,
KERN_RIGHT_EXISTS => Self::RightExists,
KERN_INVALID_HOST => Self::InvalidHost,
KERN_MEMORY_PRESENT => Self::MemoryPresent,
KERN_INVALID_PROCESSOR_SET => Self::InvalidProcessorSet,
KERN_POLICY_LIMIT => Self::PolicyLimit,
KERN_INVALID_POLICY => Self::InvalidPolicy,
KERN_INVALID_OBJECT => Self::InvalidObject,
KERN_ALREADY_WAITING => Self::AlreadyWaiting,
KERN_DEFAULT_SET => Self::DefaultSet,
KERN_EXCEPTION_PROTECTED => Self::ExceptionProtected,
KERN_INVALID_LEDGER => Self::InvalidLedger,
KERN_INVALID_MEMORY_CONTROL => Self::InvalidMemoryControl,
KERN_INVALID_SECURITY => Self::InvalidSecurity,
KERN_NOT_DEPRESSED => Self::NotDepressed,
KERN_TERMINATED => Self::Terminated,
KERN_LOCK_SET_DESTROYED => Self::LockSetDestroyed,
KERN_LOCK_UNSTABLE => Self::LockUnstable,
KERN_LOCK_OWNED => Self::LockOwned,
KERN_LOCK_OWNED_SELF => Self::LockOwnedSelf,
KERN_SEMAPHORE_DESTROYED => Self::SemaphoreDestroyed,
KERN_RPC_SERVER_TERMINATED => Self::RpcServerTerminated,
KERN_RPC_TERMINATE_ORPHAN => Self::RpcTerminateOrphan,
KERN_RPC_CONTINUE_ORPHAN => Self::RpcContinueOrphan,
KERN_NOT_SUPPORTED => Self::NotSupported,
KERN_NODE_DOWN => Self::NodeDown,
KERN_NOT_WAITING => Self::NotWaiting,
KERN_OPERATION_TIMED_OUT => Self::OperationTimedOut,
KERN_CODESIGN_ERROR => Self::CodesignError,
KERN_POLICY_STATIC => Self::PoicyStatic,
52 => Self::InsufficientBufferSize,
53 => Self::Denied,
54 => Self::MissingKC,
55 => Self::InvalidKC,
56 => Self::NotFound,
_ => Self::Failure,
}
}
}
pub const THREAD_STATE_MAX: usize = 1296;
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
pub const THREAD_STATE_FLAVOR: u32 = 4;
pub type ArchThreadState = mach2::structs::x86_thread_state64_t;
} else if #[cfg(target_arch = "aarch64")] {
pub const THREAD_STATE_FLAVOR: u32 = 6;
#[repr(C)]
pub struct Arm64ThreadState {
pub x: [u64; 29],
pub fp: u64,
pub lr: u64,
pub sp: u64,
pub pc: u64,
pub cpsr: u32,
__pad: u32,
}
pub type ArchThreadState = Arm64ThreadState;
} else {
compile_error!("unsupported target arch");
}
}
#[repr(C, align(8))]
pub struct ThreadState {
pub state: [u32; THREAD_STATE_MAX],
pub state_size: u32,
}
impl Default for ThreadState {
fn default() -> Self {
Self {
state: [0u32; THREAD_STATE_MAX],
state_size: (THREAD_STATE_MAX * std::mem::size_of::<u32>()) as u32,
}
}
}
impl ThreadState {
#[inline]
pub fn pc(&self) -> u64 {
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
self.arch_state().__rip
} else if #[cfg(target_arch = "aarch64")] {
self.arch_state().pc
}
}
}
#[inline]
pub fn sp(&self) -> u64 {
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
self.arch_state().__rsp
} else if #[cfg(target_arch = "aarch64")] {
self.arch_state().sp
}
}
}
#[inline]
pub fn arch_state(&self) -> &ArchThreadState {
unsafe { &*(self.state.as_ptr().cast()) }
}
}
pub trait TaskInfo {
const FLAVOR: u32;
}
pub trait ThreadInfo {
const FLAVOR: u32;
}
pub const MH_EXECUTE: u32 = 0x2;
pub const MH_DYLINKER: u32 = 0x7;
pub const MH_MAGIC_64: u32 = 0xfeedfacf;
#[repr(u32)]
#[derive(Debug)]
pub enum LoadCommandKind {
Segment = 0x19,
IdDylib = 0xd,
Uuid = 0x1b,
LoadDylinker = 0xe,
IdDylinker = 0xf,
}
impl LoadCommandKind {
#[inline]
fn from_u32(kind: u32) -> Option<Self> {
Some(if kind == Self::Segment as u32 {
Self::Segment
} else if kind == Self::IdDylib as u32 {
Self::IdDylib
} else if kind == Self::Uuid as u32 {
Self::Uuid
} else if kind == Self::LoadDylinker as u32 {
Self::LoadDylinker
} else if kind == Self::IdDylinker as u32 {
Self::IdDylinker
} else {
return None;
})
}
}
#[repr(C)]
#[derive(Clone)]
pub struct MachHeader {
pub magic: u32,
pub cpu_type: i32,
pub cpu_sub_type: i32,
pub file_type: u32,
pub num_commands: u32,
pub size_commands: u32,
pub flags: u32,
__reserved: u32,
}
#[repr(C)]
pub struct LoadCommandBase {
pub cmd: u32,
pub cmd_size: u32,
}
#[repr(C)]
pub struct SegmentCommand64 {
cmd: u32,
pub cmd_size: u32,
pub segment_name: [u8; 16],
pub vm_addr: u64,
pub vm_size: u64,
pub file_off: u64,
pub file_size: u64,
pub max_prot: i32,
pub init_prot: i32,
pub num_sections: u32,
pub flags: u32,
}
#[repr(C)]
#[derive(Debug)]
pub struct Dylib {
pub name: u32,
pub timestamp: u32,
pub current_version: u32,
pub compatibility_version: u32,
}
#[repr(C)]
pub struct DylibCommand {
cmd: u32,
pub cmd_size: u32,
pub dylib: Dylib,
}
#[repr(C)]
struct DylinkerCommandRepr {
cmd: u32,
cmd_size: u32,
name: u32,
}
pub struct DylinkerCommand<'buf> {
pub cmd: u32,
pub cmd_size: u32,
pub name_offset: u32,
pub name: &'buf str,
}
#[repr(C)]
pub struct UuidCommand {
cmd: u32,
pub cmd_size: u32,
pub uuid: [u8; 16],
}
pub struct LoadCommands {
pub buffer: Vec<u8>,
pub count: u32,
}
impl LoadCommands {
#[inline]
pub fn iter(&self) -> LoadCommandsIter<'_> {
LoadCommandsIter {
buffer: &self.buffer,
count: self.count,
}
}
}
pub enum LoadCommand<'buf> {
Segment(&'buf SegmentCommand64),
Dylib(&'buf DylibCommand),
Uuid(&'buf UuidCommand),
DylinkerCommand(DylinkerCommand<'buf>),
}
pub struct LoadCommandsIter<'buf> {
buffer: &'buf [u8],
count: u32,
}
impl<'buf> Iterator for LoadCommandsIter<'buf> {
type Item = LoadCommand<'buf>;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
loop {
if self.count == 0 || self.buffer.len() < std::mem::size_of::<LoadCommandBase>() {
return None;
}
let header = &*(self.buffer.as_ptr().cast::<LoadCommandBase>());
if header.cmd_size as usize > self.buffer.len() {
return None;
}
let cmd = LoadCommandKind::from_u32(header.cmd).and_then(|kind| {
Some(match kind {
LoadCommandKind::Segment => LoadCommand::Segment(
&*(self.buffer.as_ptr().cast::<SegmentCommand64>()),
),
LoadCommandKind::IdDylib => {
LoadCommand::Dylib(&*(self.buffer.as_ptr().cast::<DylibCommand>()))
}
LoadCommandKind::Uuid => {
LoadCommand::Uuid(&*(self.buffer.as_ptr().cast::<UuidCommand>()))
}
LoadCommandKind::LoadDylinker | LoadCommandKind::IdDylinker => {
let dcr = &*(self.buffer.as_ptr().cast::<DylinkerCommandRepr>());
let nul = self.buffer[dcr.name as usize..header.cmd_size as usize]
.iter()
.position(|c| *c == 0)?;
LoadCommand::DylinkerCommand(DylinkerCommand {
cmd: dcr.cmd,
cmd_size: dcr.cmd_size,
name_offset: dcr.name,
name: std::str::from_utf8(
&self.buffer[dcr.name as usize..dcr.name as usize + nul],
)
.ok()?,
})
}
})
});
self.count -= 1;
self.buffer = &self.buffer[header.cmd_size as usize..];
if let Some(cmd) = cmd {
return Some(cmd);
}
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let sz = self.count as usize;
(sz, Some(sz))
}
}
pub fn sysctl_by_name<T: Sized + Default>(name: &[u8]) -> T {
let mut out = T::default();
let mut len = std::mem::size_of_val(&out);
unsafe {
if libc::sysctlbyname(
name.as_ptr().cast(),
(&mut out as *mut T).cast(),
&mut len,
std::ptr::null_mut(),
0,
) != 0
{
log::warn!("failed to get sysctl for {name:?}");
T::default()
} else {
out
}
}
}
pub fn int_sysctl_by_name<T: TryFrom<i32> + Default>(name: &[u8]) -> T {
let val = sysctl_by_name::<i32>(name);
T::try_from(val).unwrap_or_default()
}
pub fn sysctl_string(name: &[u8]) -> String {
let mut buf_len = 0;
let string_buf = unsafe {
if libc::sysctlbyname(
name.as_ptr().cast(),
std::ptr::null_mut(),
&mut buf_len,
std::ptr::null_mut(),
0,
) != 0
|| buf_len <= 1
{
return String::new();
}
let mut buff = Vec::new();
buff.resize(buf_len, 0);
if libc::sysctlbyname(
name.as_ptr().cast(),
buff.as_mut_ptr().cast(),
&mut buf_len,
std::ptr::null_mut(),
0,
) != 0
{
return String::new();
}
buff.pop(); buff
};
String::from_utf8(string_buf).unwrap_or_default()
}
extern "C" {
pub fn pid_for_task(task: mach_port_name_t, pid: *mut i32) -> kern_return_t;
pub fn thread_info(
thread: u32,
flavor: u32,
thread_info: *mut i32,
info_size: *mut u32,
) -> kern_return_t;
}