blob: 547058cdef3e82b20890b235d22744c8f80f184f [file] [log] [blame] [edit]
#![deny(missing_docs)]
//! A library for manipulating memory regions
//!
//! This crate provides several functions for handling memory pages and regions.
//! It is implemented using platform specific APIs. The library exposes both low
//! and high level functionality for manipulating pages.
//!
//! Not all OS specific quirks are abstracted away. For instance; some OSs
//! enforce memory pages to be readable whilst other may prevent pages from
//! becoming executable (i.e DEP).
//!
//! *Note: a region is a collection of one or more pages laying consecutively in
//! memory, with the same properties.*
//!
//! # Installation
//!
//! This crate is [on crates.io](https://crates.io/crates/region) and can be
//! used by adding `region` to your dependencies in your project's `Cargo.toml`.
//!
//! ```toml
//! [dependencies]
//! region = "2.2.0"
//! ```
//!
//! and this to your crate root:
//!
//! ```rust
//! extern crate region;
//! ```
//!
//! # Examples
//!
//! - Cross-platform equivalents.
//!
//! ```rust
//! # unsafe fn example() -> region::Result<()> {
//! # use region::Protection;
//! let data = [0xDE, 0xAD, 0xBE, 0xEF];
//!
//! // Page size
//! let pz = region::page::size();
//! let pc = region::page::ceil(1234);
//! let pf = region::page::floor(1234);
//!
//! // VirtualQuery | '/proc/self/maps'
//! let q = region::query(data.as_ptr())?;
//! let qr = region::query_range(data.as_ptr(), data.len())?;
//!
//! // VirtualProtect | mprotect
//! region::protect(data.as_ptr(), data.len(), Protection::READ_WRITE_EXECUTE)?;
//!
//! // ... you can also temporarily change a region's protection
//! let handle = region::protect_with_handle(data.as_ptr(), data.len(), Protection::READ_WRITE_EXECUTE)?;
//!
//! // VirtualLock | mlock
//! let guard = region::lock(data.as_ptr(), data.len())?;
//! # Ok(())
//! # }
//! ```
#[macro_use]
extern crate bitflags;
extern crate libc;
pub use error::{Error, Result};
pub use lock::{lock, unlock, LockGuard};
pub use protect::{protect, protect_with_handle, ProtectGuard, Protection};
mod error;
mod lock;
mod os;
pub mod page;
mod protect;
/// A descriptor for a memory region
///
/// This type acts as a POD-type, i.e it has no functionality but merely
/// stores region information.
#[derive(Debug, Clone, Copy)]
pub struct Region {
/// Base address of the region
pub base: *const u8,
/// Whether the region is guarded or not
pub guarded: bool,
/// Protection of the region
pub protection: Protection,
/// Whether the region is shared or not
pub shared: bool,
/// Size of the region (multiple of page size)
pub size: usize,
}
impl Region {
/// Returns the region's lower bound.
pub fn lower(&self) -> usize {
self.base as usize
}
/// Returns the region's upper bound.
pub fn upper(&self) -> usize {
self.lower() + self.size
}
}
unsafe impl Send for Region {}
unsafe impl Sync for Region {}
/// Queries the OS with an address, returning the region it resides within.
///
/// The implementation uses `VirtualQuery` on Windows, `mach_vm_region` on macOS,
/// `kinfo_getvmmap` on FreeBSD, and parses `proc/[pid]/maps` on Linux.
///
/// - The enclosing region can be of multiple page sizes.
/// - The address is rounded down to the closest page boundary.
/// - The address may not be null.
///
/// # Examples
///
/// ```
/// use region::{Protection};
///
/// let data = [0; 100];
/// let region = region::query(data.as_ptr()).unwrap();
///
/// assert_eq!(region.protection, Protection::READ_WRITE);
/// ```
pub fn query(address: *const u8) -> Result<Region> {
if address.is_null() {
return Err(Error::NullAddress);
}
// The address must be aligned to the closest page boundary
os::get_region(page::floor(address as usize) as *const u8)
}
/// Queries the OS with a range, returning the regions it contains.
///
/// A 2-byte range straddling a page boundary will return both pages (or one
/// region, if the pages have the same properties). The implementation uses
/// `query` internally.
///
/// - The range is `[address, address + size)`
/// - The address is rounded down to the closest page boundary.
/// - The address may not be null.
/// - The size may not be zero.
///
/// # Examples
///
/// ```
/// let data = [0; 100];
/// let region = region::query_range(data.as_ptr(), data.len()).unwrap();
///
/// assert!(region.len() > 0);
/// ```
pub fn query_range(address: *const u8, size: usize) -> Result<Vec<Region>> {
if size == 0 {
return Err(Error::EmptyRange);
}
let mut result = Vec::new();
let mut base = page::floor(address as usize);
let limit = address as usize + size;
loop {
let region = query(base as *const u8)?;
result.push(region);
base = region.upper();
if limit <= region.upper() {
break;
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
extern crate memmap;
use self::memmap::MmapMut;
use super::*;
pub fn alloc_pages(prots: &[Protection]) -> MmapMut {
let pz = page::size();
let map = MmapMut::map_anon(pz * prots.len()).unwrap();
let mut base = map.as_ptr();
for protection in prots {
unsafe {
protect(base, pz, *protection).unwrap();
base = base.offset(pz as isize);
}
}
map
}
#[test]
fn query_null() {
assert!(query(::std::ptr::null()).is_err());
}
#[test]
fn query_code() {
let region = query(query_code as *const () as *const u8).unwrap();
assert_eq!(region.guarded, false);
assert_eq!(region.shared, cfg!(windows));
}
#[test]
fn query_alloc() {
let size = page::size() * 2;
let mut map = alloc_pages(&[Protection::READ_EXECUTE, Protection::READ_EXECUTE]);
let region = query(map.as_ptr()).unwrap();
assert_eq!(region.guarded, false);
assert_eq!(region.protection, Protection::READ_EXECUTE);
assert!(!region.base.is_null() && region.base <= map.as_mut_ptr());
assert!(region.size >= size);
}
#[test]
fn query_area_zero() {
assert!(query_range(&query_area_zero as *const _ as *const u8, 0).is_err());
}
#[test]
fn query_area_overlap() {
let pz = page::size();
let prots = [Protection::READ_EXECUTE, Protection::READ_WRITE];
let map = alloc_pages(&prots);
// Query an area that overlaps both pages
let address = unsafe { map.as_ptr().offset(pz as isize - 1) };
let result = query_range(address, 2).unwrap();
assert_eq!(result.len(), prots.len());
for i in 0..prots.len() {
assert_eq!(result[i].protection, prots[i]);
}
}
#[test]
fn query_area_alloc() {
let pz = page::size();
let prots = [
Protection::READ,
Protection::READ_WRITE,
Protection::READ_EXECUTE,
];
let map = alloc_pages(&prots);
// Confirm only one page is retrieved
let result = query_range(map.as_ptr(), pz).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].protection, prots[0]);
// Retrieve all allocated pages
let result = query_range(map.as_ptr(), pz * prots.len()).unwrap();
assert_eq!(result.len(), prots.len());
assert_eq!(result[1].size, pz);
for i in 0..prots.len() {
assert_eq!(result[i].protection, prots[i]);
}
}
}