#[cfg(debug_assertions)]
use crate::utils::logger;
use mach2::{
kern_return::KERN_SUCCESS,
traps::mach_task_self,
vm::mach_vm_region,
vm_prot::{VM_PROT_EXECUTE, VM_PROT_READ, VM_PROT_WRITE},
vm_region::{VM_REGION_BASIC_INFO_64, vm_region_basic_info_64, vm_region_info_t},
vm_types::{mach_vm_address_t, mach_vm_size_t},
};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ProtectionError {
#[error("Failed to query region at {0:#x}")]
QueryFailed(usize),
#[error("Invalid address {0:#x}")]
InvalidAddress(usize),
#[error("Protection failed: {0}")]
ProtectionFailed(i32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PageProtection {
flags: i32,
}
impl PageProtection {
pub fn from_raw(flags: i32) -> Self {
Self { flags }
}
pub fn raw(&self) -> i32 {
self.flags
}
pub fn is_readable(&self) -> bool {
(self.flags & VM_PROT_READ) != 0
}
pub fn is_writable(&self) -> bool {
(self.flags & VM_PROT_WRITE) != 0
}
pub fn is_executable(&self) -> bool {
(self.flags & VM_PROT_EXECUTE) != 0
}
pub fn read_only() -> Self {
Self {
flags: VM_PROT_READ,
}
}
pub fn read_write() -> Self {
Self {
flags: VM_PROT_READ | VM_PROT_WRITE,
}
}
pub fn read_execute() -> Self {
Self {
flags: VM_PROT_READ | VM_PROT_EXECUTE,
}
}
}
#[derive(Debug, Clone)]
pub struct RegionInfo {
pub address: usize,
pub size: usize,
pub protection: PageProtection,
}
pub fn get_protection(addr: usize) -> Result<PageProtection, ProtectionError> {
let info = get_region_info(addr)?;
Ok(info.protection)
}
pub fn get_region_info(addr: usize) -> Result<RegionInfo, ProtectionError> {
let region = find_region(addr)?;
if region.address > addr {
return Err(ProtectionError::InvalidAddress(addr));
}
Ok(region)
}
pub fn find_region(addr: usize) -> Result<RegionInfo, ProtectionError> {
unsafe {
let task = mach_task_self();
let mut address = addr as mach_vm_address_t;
let mut region_size: mach_vm_size_t = 0;
let mut info = vm_region_basic_info_64::default();
let mut info_count = VM_REGION_BASIC_INFO_64;
let mut object_name = 0;
let kr = mach_vm_region(
task,
&mut address,
&mut region_size,
VM_REGION_BASIC_INFO_64,
&mut info as *mut _ as *mut i32,
&mut info_count as *mut _ as *mut u32,
&mut object_name,
);
if kr != KERN_SUCCESS {
return Err(ProtectionError::QueryFailed(addr));
}
Ok(RegionInfo {
address: address as usize,
size: region_size as usize,
protection: PageProtection::from_raw(info.protection),
})
}
}
pub fn is_readable(addr: usize) -> bool {
get_protection(addr)
.map(|p| p.is_readable())
.unwrap_or(false)
}
pub fn is_writable(addr: usize) -> bool {
get_protection(addr)
.map(|p| p.is_writable())
.unwrap_or(false)
}
pub fn is_executable(addr: usize) -> bool {
get_protection(addr)
.map(|p| p.is_executable())
.unwrap_or(false)
}
pub fn protect(
addr: usize,
size: usize,
protection: PageProtection,
) -> Result<(), ProtectionError> {
unsafe {
let task = mach_task_self();
let kr = mach2::vm::mach_vm_protect(
task,
addr as mach_vm_address_t,
size as mach_vm_size_t,
0,
protection.raw(),
);
if kr != KERN_SUCCESS {
return Err(ProtectionError::ProtectionFailed(kr));
}
Ok(())
}
}
pub fn get_all_regions() -> Result<Vec<RegionInfo>, ProtectionError> {
let mut regions = Vec::new();
let task = unsafe { mach_task_self() };
let mut address: mach_vm_address_t = 0;
loop {
let mut size: mach_vm_size_t = 0;
let mut info = vm_region_basic_info_64::default();
let mut info_count = vm_region_basic_info_64::count();
let mut object_name: u32 = 0;
let kr = unsafe {
mach_vm_region(
task,
&mut address,
&mut size,
VM_REGION_BASIC_INFO_64,
&mut info as *mut _ as vm_region_info_t,
&mut info_count,
&mut object_name,
)
};
if kr != KERN_SUCCESS {
break;
}
if (info.protection & VM_PROT_READ) != 0 {
regions.push(RegionInfo {
address: address as usize,
size: size as usize,
protection: PageProtection::from_raw(info.protection),
});
}
address += size;
}
#[cfg(debug_assertions)]
logger::info(&format!("Found {} memory regions", regions.len()));
Ok(regions)
}