Skip to main content

specter/memory/info/
protection.rs

1//! Memory page protection query utilities
2
3#[cfg(feature = "dev_release")]
4use crate::utils::logger;
5use mach2::{
6    kern_return::KERN_SUCCESS,
7    traps::mach_task_self,
8    vm::mach_vm_region,
9    vm_prot::{VM_PROT_EXECUTE, VM_PROT_READ, VM_PROT_WRITE},
10    vm_region::{VM_REGION_BASIC_INFO_64, vm_region_basic_info_64, vm_region_info_t},
11    vm_types::{mach_vm_address_t, mach_vm_size_t},
12};
13use thiserror::Error;
14
15#[derive(Error, Debug)]
16/// Errors that can occur during protection queries or modifications
17pub enum ProtectionError {
18    /// Failed to query memory region information from the kernel
19    #[error("Failed to query region at {0:#x}")]
20    QueryFailed(usize),
21    /// The address is invalid or not mapped
22    #[error("Invalid address {0:#x}")]
23    InvalidAddress(usize),
24    /// Failed to change memory protection
25    #[error("Protection failed: {0}")]
26    ProtectionFailed(i32),
27}
28
29/// Represents memory page protection flags
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct PageProtection {
32    flags: i32,
33}
34
35impl PageProtection {
36    /// Creates a new PageProtection from raw VM_PROT flags
37    pub fn from_raw(flags: i32) -> Self {
38        Self { flags }
39    }
40
41    /// Returns the raw VM_PROT flags
42    pub fn raw(&self) -> i32 {
43        self.flags
44    }
45
46    /// Checks if the page is readable
47    pub fn is_readable(&self) -> bool {
48        (self.flags & VM_PROT_READ) != 0
49    }
50
51    /// Checks if the page is writable
52    pub fn is_writable(&self) -> bool {
53        (self.flags & VM_PROT_WRITE) != 0
54    }
55
56    /// Checks if the page is executable
57    pub fn is_executable(&self) -> bool {
58        (self.flags & VM_PROT_EXECUTE) != 0
59    }
60
61    /// Creates a read-only protection
62    pub fn read_only() -> Self {
63        Self {
64            flags: VM_PROT_READ,
65        }
66    }
67
68    /// Creates a read-write protection
69    pub fn read_write() -> Self {
70        Self {
71            flags: VM_PROT_READ | VM_PROT_WRITE,
72        }
73    }
74
75    /// Creates a read-execute protection
76    pub fn read_execute() -> Self {
77        Self {
78            flags: VM_PROT_READ | VM_PROT_EXECUTE,
79        }
80    }
81}
82
83/// Represents detailed information about a memory region
84#[derive(Debug, Clone)]
85pub struct RegionInfo {
86    /// Base address of the region
87    pub address: usize,
88    /// Size of the region in bytes
89    pub size: usize,
90    /// Current protection flags
91    pub protection: PageProtection,
92}
93
94/// Queries the current protection flags for a memory address
95///
96/// # Arguments
97/// * `addr` - The address to query
98///
99/// # Returns
100/// * `Result<PageProtection, ProtectionError>` - The protection flags or an error
101pub fn get_protection(addr: usize) -> Result<PageProtection, ProtectionError> {
102    let info = get_region_info(addr)?;
103    Ok(info.protection)
104}
105
106/// Queries detailed region information for a memory address
107///
108/// # Arguments
109/// * `addr` - The address to query
110///
111/// # Returns
112/// * `Result<RegionInfo, ProtectionError>` - The region information or an error
113pub fn get_region_info(addr: usize) -> Result<RegionInfo, ProtectionError> {
114    let region = find_region(addr)?;
115
116    if region.address > addr {
117        return Err(ProtectionError::InvalidAddress(addr));
118    }
119
120    Ok(region)
121}
122
123/// Finds the memory region containing or following the given address
124///
125/// # Arguments
126/// * `addr` - The address to search from
127///
128/// # Returns
129/// * `Result<RegionInfo, ProtectionError>` - The found region or an error
130pub fn find_region(addr: usize) -> Result<RegionInfo, ProtectionError> {
131    unsafe {
132        let task = mach_task_self();
133        let mut address = addr as mach_vm_address_t;
134        let mut region_size: mach_vm_size_t = 0;
135        let mut info = vm_region_basic_info_64::default();
136        let mut info_count = VM_REGION_BASIC_INFO_64;
137        let mut object_name = 0;
138
139        let kr = mach_vm_region(
140            task,
141            &mut address,
142            &mut region_size,
143            VM_REGION_BASIC_INFO_64,
144            &mut info as *mut _ as *mut i32,
145            &mut info_count as *mut _ as *mut u32,
146            &mut object_name,
147        );
148
149        if kr != KERN_SUCCESS {
150            return Err(ProtectionError::QueryFailed(addr));
151        }
152
153        Ok(RegionInfo {
154            address: address as usize,
155            size: region_size as usize,
156            protection: PageProtection::from_raw(info.protection),
157        })
158    }
159}
160
161/// Quick check if an address is readable
162pub fn is_readable(addr: usize) -> bool {
163    get_protection(addr)
164        .map(|p| p.is_readable())
165        .unwrap_or(false)
166}
167
168/// Quick check if an address is writable
169pub fn is_writable(addr: usize) -> bool {
170    get_protection(addr)
171        .map(|p| p.is_writable())
172        .unwrap_or(false)
173}
174
175/// Quick check if an address is executable
176pub fn is_executable(addr: usize) -> bool {
177    get_protection(addr)
178        .map(|p| p.is_executable())
179        .unwrap_or(false)
180}
181
182/// Changes the protection of a memory region
183///
184/// # Arguments
185/// * `addr` - The start address
186/// * `size` - The size of the region
187/// * `protection` - The new protection flags
188///
189/// # Returns
190/// * `Result<(), ProtectionError>` - Result indicating success or failure
191pub fn protect(
192    addr: usize,
193    size: usize,
194    protection: PageProtection,
195) -> Result<(), ProtectionError> {
196    unsafe {
197        let task = mach_task_self();
198        let kr = mach2::vm::mach_vm_protect(
199            task,
200            addr as mach_vm_address_t,
201            size as mach_vm_size_t,
202            0,
203            protection.raw(),
204        );
205
206        if kr != KERN_SUCCESS {
207            return Err(ProtectionError::ProtectionFailed(kr));
208        }
209        Ok(())
210    }
211}
212
213/// Enumerates all readable memory regions in the process
214///
215/// # Returns
216/// * `Result<Vec<RegionInfo>, ProtectionError>` - A list of regions or an error
217pub fn get_all_regions() -> Result<Vec<RegionInfo>, ProtectionError> {
218    let mut regions = Vec::new();
219    let task = unsafe { mach_task_self() };
220    let mut address: mach_vm_address_t = 0;
221
222    loop {
223        let mut size: mach_vm_size_t = 0;
224        let mut info = vm_region_basic_info_64::default();
225        let mut info_count = vm_region_basic_info_64::count();
226        let mut object_name: u32 = 0;
227
228        let kr = unsafe {
229            mach_vm_region(
230                task,
231                &mut address,
232                &mut size,
233                VM_REGION_BASIC_INFO_64,
234                &mut info as *mut _ as vm_region_info_t,
235                &mut info_count,
236                &mut object_name,
237            )
238        };
239
240        if kr != KERN_SUCCESS {
241            break;
242        }
243
244        if (info.protection & VM_PROT_READ) != 0 {
245            regions.push(RegionInfo {
246                address: address as usize,
247                size: size as usize,
248                protection: PageProtection::from_raw(info.protection),
249            });
250        }
251
252        address += size;
253    }
254
255    #[cfg(feature = "dev_release")]
256    logger::info(&format!("Found {} memory regions", regions.len()));
257    Ok(regions)
258}