#![cfg_attr(feature = "fn_traits", feature(fn_traits))]
#![cfg_attr(feature = "fn_traits", feature(unboxed_closures))]
#![warn(missing_docs)]
#![warn(rustdoc::missing_doc_code_examples)]
#[cfg(any(feature = "allow_exec", doc, test))]
mod extern_fn_ptr;
mod paged_vec;
#[cfg(any(feature = "allow_exec", doc, test))]
use core::fmt::Pointer;
#[cfg(any(feature = "allow_exec", doc, test))]
mod fn_ref;
#[cfg(any(feature = "allow_exec", doc, test))]
use extern_fn_ptr::ExternFnPtr;
#[doc(inline)]
#[cfg(any(feature = "allow_exec", doc, test))]
pub use fn_ref::*;
#[doc(inline)]
pub use paged_vec::*;
use std::borrow::{Borrow, BorrowMut};
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
#[cfg(target_family = "windows")]
use winapi::um::memoryapi::*;
#[cfg(target_family = "windows")]
use winapi::um::winnt::{
MEM_COMMIT, MEM_RELEASE, PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE,
PAGE_NOACCESS, PAGE_READONLY, PAGE_READWRITE,
};
const fn next_page_boundary(size: usize) -> usize {
((size + PAGE_SIZE - 1) / PAGE_SIZE) * PAGE_SIZE
}
const PAGE_SIZE: usize = 0x1000;
#[cfg(target_family = "unix")]
const MAP_ANYNOMUS: c_int = 0x20;
#[cfg(target_family = "unix")]
const MAP_PRIVATE: c_int = 0x2;
#[cfg(target_family = "unix")]
const NO_FILE: c_int = -1;
#[cfg(target_family = "unix")]
use std::ffi::{c_int, c_void};
#[cfg(target_family = "unix")]
extern "C" {
fn mmap(
addr: *mut c_void,
length: usize,
prot: c_int,
flags: c_int,
fd: c_int,
offset: usize,
) -> *mut c_void;
fn munmap(addr: *mut c_void, length: usize) -> c_int;
fn mprotect(addr: *mut c_void, len: usize, prot: c_int) -> c_int;
fn strerror(errnum: c_int) -> *const i8;
fn mremap(old_addr: *mut c_void, old_size: usize, new_size: usize, flags: c_int)
-> *mut c_void;
fn posix_madvise(addr: *mut c_void, length: usize, advice: c_int) -> c_int;
}
pub trait ReadPremisionMarker {
#[cfg(all(target_family = "unix"))]
#[doc(hidden)]
fn bitmask() -> c_int;
#[doc(hidden)]
fn allow_read() -> bool;
}
pub trait WritePremisionMarker {
#[cfg(target_family = "unix")]
#[doc(hidden)]
fn bitmask() -> c_int;
#[doc(hidden)]
fn allow_write() -> bool;
}
pub trait ExecPremisionMarker {
#[cfg(target_family = "unix")]
#[doc(hidden)]
fn bitmask() -> c_int;
#[doc(hidden)]
fn allow_exec() -> bool;
}
pub struct AllowRead;
impl ReadPremisionMarker for AllowRead {
#[cfg(target_family = "unix")]
fn bitmask() -> c_int {
0x1
}
fn allow_read() -> bool {
true
}
}
pub struct DenyRead;
impl ReadPremisionMarker for DenyRead {
#[cfg(target_family = "unix")]
fn bitmask() -> c_int {
0
}
fn allow_read() -> bool {
false
}
}
pub struct AllowWrite;
impl WritePremisionMarker for AllowWrite {
#[cfg(target_family = "unix")]
fn bitmask() -> c_int {
0x2
}
fn allow_write() -> bool {
true
}
}
pub struct DenyWrite;
impl WritePremisionMarker for DenyWrite {
#[cfg(target_family = "unix")]
fn bitmask() -> c_int {
0
}
fn allow_write() -> bool {
false
}
}
#[cfg(any(feature = "allow_exec", doc, test))]
pub struct AllowExec;
#[cfg(any(feature = "allow_exec", doc, test))]
impl ExecPremisionMarker for AllowExec {
#[cfg(target_family = "unix")]
fn bitmask() -> c_int {
0x4
}
fn allow_exec() -> bool {
true
}
}
pub struct DenyExec;
impl ExecPremisionMarker for DenyExec {
#[cfg(target_family = "unix")]
fn bitmask() -> c_int {
0
}
fn allow_exec() -> bool {
false
}
}
pub struct Pages<R: ReadPremisionMarker, W: WritePremisionMarker, E: ExecPremisionMarker> {
ptr: *mut u8,
len: usize,
read: PhantomData<R>,
write: PhantomData<W>,
exec: PhantomData<E>,
}
#[cfg(target_family = "unix")]
fn erno() -> c_int {
#[cfg(any(target_os = "linux", target_os = "redox"))]
{
extern "C" {
fn __errno_location() -> *mut c_int;
}
unsafe { *__errno_location() }
}
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
{
extern "C" {
fn ___errno() -> *mut c_int;
}
unsafe { *___errno() }
}
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
{
extern "C" {
fn __error() -> *mut c_int;
}
unsafe { *__error() }
}
}
#[cfg(target_family = "unix")]
fn errno_msg() -> String {
let cstr = unsafe { std::ffi::CStr::from_ptr(strerror(erno())) };
String::from_utf8_lossy(cstr.to_bytes()).to_string()
}
impl<R: ReadPremisionMarker, W: WritePremisionMarker, E: ExecPremisionMarker> Pages<R, W, E> {
#[cfg(target_family = "unix")]
fn bitmask() -> c_int {
R::bitmask() | W::bitmask() | E::bitmask()
}
#[cfg(target_family = "windows")]
fn flProtect() -> u32 {
let mask = (R::allow_read() as u8 * 0x1)
| (W::allow_write() as u8 * 0x2)
| (E::allow_exec() as u8 * 0x4);
match mask {
0x0 => PAGE_NOACCESS,
0x1 => PAGE_READONLY,
0x2 => PAGE_READWRITE, 0x3 => PAGE_READWRITE,
0x4 => PAGE_EXECUTE,
0x5 => PAGE_EXECUTE_READ,
0x6 => PAGE_EXECUTE_READWRITE, 0x7 => PAGE_EXECUTE_READWRITE,
0x8..=0xFF => panic!("Invalid protection mask:{mask}"),
}
}
#[must_use]
pub fn new(length: usize) -> Self {
Self::new_native(length)
}
pub fn advise_use_soon(&mut self, used: usize) {
#[cfg(target_family = "unix")]
unsafe {
let ad_len = self.len.min(used);
const POSIX_MADV_WILLNEED: c_int = 3;
posix_madvise(self.ptr as *mut c_void, ad_len, POSIX_MADV_WILLNEED);
}
}
pub fn advise_use_seq(&mut self) {
#[cfg(target_family = "unix")]
unsafe {
const POSIX_MADV_SEQUENTIAL: c_int = 2;
posix_madvise(self.ptr as *mut c_void, self.len, POSIX_MADV_SEQUENTIAL);
}
}
pub fn advise_use_rnd(&mut self) {
#[cfg(target_family = "unix")]
unsafe {
const POSIX_MADV_RANDOM: c_int = 1;
posix_madvise(self.ptr as *mut c_void, self.len, POSIX_MADV_RANDOM);
}
}
#[cfg(target_family = "windows")]
fn new_native(length: usize) -> Self {
assert_ne!(length, 0, "0 - sized allcations are not allowed!");
let len = next_page_boundary(length);
let ptr =
unsafe { VirtualAlloc(std::ptr::null_mut(), length, MEM_COMMIT, Self::flProtect()) }
.cast::<u8>();
if ptr.is_null(){
let err = unsafe { winapi::um::errhandlingapi::GetLastError() };
panic!("Allocation using VirtualAlloc failed with error code:{err}!");
}
Self {
ptr,
len,
read: PhantomData,
write: PhantomData,
exec: PhantomData,
}
}
#[cfg(target_family = "unix")]
fn new_native(length: usize) -> Self {
assert_ne!(length, 0, "0 - sized allcations are not allowed!");
let len = next_page_boundary(length);
let prot_mask = Self::bitmask();
let ptr = unsafe {
mmap(
std::ptr::null_mut(),
len,
prot_mask,
MAP_ANYNOMUS | MAP_PRIVATE,
NO_FILE,
0,
)
}
.cast::<u8>();
if ptr as usize == usize::MAX {
let erno = errno_msg();
panic!("mmap error, erno:{erno:?}!");
}
Self {
ptr,
len,
read: PhantomData,
write: PhantomData,
exec: PhantomData,
}
}
#[cfg(target_family = "unix")]
fn set_prot(&mut self) {
let mask = Self::bitmask();
if unsafe { mprotect(self.ptr.cast::<c_void>(), self.len, mask) } != -1 && erno() != 0 {
let err = errno_msg();
panic!("Failed to change memory protection mode:'{err}'!");
}
}
#[cfg(target_family = "windows")]
fn set_prot(&mut self) {
let mut _old: u32 = 0;
let res = unsafe {
winapi::um::memoryapi::VirtualProtect(
self.ptr.cast::<winapi::ctypes::c_void>(),
self.len,
Self::flProtect(),
&mut _old as *mut _,
)
};
if res == 0 {
let err = unsafe { winapi::um::errhandlingapi::GetLastError() };
panic!("Changing memory protection using using VirtualProtect failed with error code:{err}!");
}
}
fn into_prot<TR: ReadPremisionMarker, TW: WritePremisionMarker, TE: ExecPremisionMarker>(
self,
) -> Pages<TR, TW, TE> {
let mut res = Pages {
ptr: self.ptr,
len: self.len,
read: PhantomData,
write: PhantomData,
exec: PhantomData,
};
std::mem::forget(self);
#[cfg(target_family = "unix")]
if Self::bitmask() == (Pages::<TR, TW, TE>::bitmask()) {
return res;
}
#[cfg(target_family = "windows")]
if Self::flProtect() == (Pages::<TR, TW, TE>::flProtect()) {
return res;
}
res.set_prot();
res
}
pub fn decommit(&mut self, beginning: usize, length: usize) {
let decommit_len = length.min(self.len - beginning);
#[cfg(target_os = "windows")]
unsafe {
let res = DiscardVirtualMemory(
(self.ptr as usize + beginning) as *mut winapi::ctypes::c_void,
decommit_len,
);
if (res != 0) && cfg!(debug_assertions) {
panic!("DiscardVirtualMemory failed.");
}
}
#[cfg(target_family = "unix")]
unsafe {
const MADV_DONTNEED: c_int = 4;
posix_madvise(
(self.ptr as usize + beginning) as *mut c_void,
decommit_len,
MADV_DONTNEED,
);
}
}
}
impl<E: ExecPremisionMarker> Pages<AllowRead, AllowWrite, E> {
pub fn resize(&mut self, new_size: usize) {
#[cfg(target_family = "unix")]
unsafe {
const MREMAP_MAYMOVE: c_int = 1;
let ptr = mremap(self.ptr as *mut c_void, self.len, new_size, MREMAP_MAYMOVE);
if ptr as usize == usize::MAX {
let erno = errno_msg();
panic!("mmap error, erno:{erno:?}!");
}
self.ptr = ptr as *mut u8;
self.len = new_size;
}
#[cfg(not(target_family = "unix"))]
{
let mut copy = Self::new(new_size);
let copy_size = copy.len().min(self.len());
copy.split_at_mut(copy_size)
.0
.copy_from_slice(self.split_at_mut(copy_size).0);
*self = copy;
}
}
}
impl<W: WritePremisionMarker, E: ExecPremisionMarker> std::ops::Index<usize>
for Pages<AllowRead, W, E>
{
type Output = u8;
fn index(&self, index: usize) -> &u8 {
let slice: &[u8] = self;
&slice[index]
}
}
impl<W: WritePremisionMarker, E: ExecPremisionMarker> Borrow<[u8]> for Pages<AllowRead, W, E> {
fn borrow(&self) -> &[u8] {
self
}
}
impl<W: WritePremisionMarker, E: ExecPremisionMarker> Deref for Pages<AllowRead, W, E> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
}
}
impl<E: ExecPremisionMarker> DerefMut for Pages<AllowRead, AllowWrite, E> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
}
}
impl<E: ExecPremisionMarker> BorrowMut<[u8]> for Pages<AllowRead, AllowWrite, E> {
fn borrow_mut(&mut self) -> &mut [u8] {
self
}
}
impl<E: ExecPremisionMarker> std::ops::IndexMut<usize> for Pages<AllowRead, AllowWrite, E> {
fn index_mut(&mut self, index: usize) -> &mut u8 {
unsafe { &mut std::slice::from_raw_parts_mut(self.ptr, self.len)[index] }
}
}
impl<R: ReadPremisionMarker, W: WritePremisionMarker, E: ExecPremisionMarker> Pages<R, W, E> {
#[must_use]
pub fn allow_read(self) -> Pages<AllowRead, W, E> {
self.into_prot()
}
#[must_use]
pub fn deny_read(self) -> Pages<DenyRead, W, E> {
self.into_prot()
}
#[must_use]
pub fn allow_write(self) -> Pages<R, AllowWrite, E> {
self.into_prot()
}
#[must_use]
pub fn deny_write(self) -> Pages<R, DenyWrite, E> {
self.into_prot()
}
#[must_use]
pub fn allow_write_no_exec(self) -> Pages<R, AllowWrite, DenyExec> {
self.into_prot()
}
#[must_use]
#[cfg(any(feature = "allow_exec", doc, test))]
pub fn allow_exec(self) -> Pages<R, W, AllowExec> {
self.into_prot()
}
#[must_use]
#[cfg(any(feature = "allow_exec", doc, test))]
pub fn set_protected_exec(self) -> Pages<R, DenyWrite, AllowExec> {
self.into_prot()
}
#[must_use]
#[cfg(any(feature = "allow_exec", doc, test))]
pub fn deny_exec(self) -> Pages<R, W, DenyExec> {
self.into_prot()
}
}
impl<W: WritePremisionMarker, E: ExecPremisionMarker> Pages<AllowRead, W, E> {
#[must_use]
pub fn get_ptr(&self, offset: usize) -> *const u8 {
std::ptr::addr_of!(self[offset])
}
}
impl<R: ReadPremisionMarker, E: ExecPremisionMarker> Pages<R, AllowWrite, E> {
pub fn get_ptr_mut(&mut self, offset: usize) -> *mut u8 {
unsafe {
std::ptr::addr_of_mut!(std::slice::from_raw_parts_mut(self.ptr, self.len)[offset])
}
}
}
#[cfg(any(feature = "allow_exec", doc, test))]
impl<R: ReadPremisionMarker, W: WritePremisionMarker> Pages<R, W, AllowExec> {
#[must_use]
pub fn get_fn_ptr(&self, offset: usize) -> *const () {
unsafe { std::ptr::addr_of!(std::slice::from_raw_parts(self.ptr, self.len)[offset]).cast() }
}
#[must_use]
pub unsafe fn get_fn<F: ExternFnPtr>(&self, offset: usize) -> FnRef<F>
where
F: Copy + Pointer + Sized,
{
let fn_ptr = self.get_fn_ptr(offset);
let f: F = *(std::ptr::addr_of!(fn_ptr).cast::<F>());
let _ = fn_ptr;
FnRef::new(f, self)
}
}
impl<R: ReadPremisionMarker, W: WritePremisionMarker, E: ExecPremisionMarker> Drop
for Pages<R, W, E>
{
fn drop(&mut self) {
#[cfg(target_family = "unix")]
unsafe {
let res = munmap(self.ptr.cast::<c_void>(), self.len);
if res == -1 {
let err = errno_msg();
panic!("Unampping memory Pages failed. Reason:{err}");
}
}
#[cfg(target_family = "windows")]
unsafe {
let res = VirtualFree(self.ptr.cast::<winapi::ctypes::c_void>(), 0, MEM_RELEASE);
if res == 0 {
let err = winapi::um::errhandlingapi::GetLastError();
panic!("Allocation using VirtualFree failed with error code:{err}!");
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
#[cfg(feature = "allow_exec")]
fn test_alloc_rwe() {
let _pages: Pages<AllowRead, AllowWrite, AllowExec> = Pages::new(256);
}
#[test]
fn test_alloc_rw() {
let _pages: Pages<AllowRead, AllowWrite, DenyExec> = Pages::new(256);
}
#[test]
fn test_alloc_r() {
let _pages: Pages<AllowRead, DenyWrite, DenyExec> = Pages::new(256);
}
#[test]
#[cfg(feature = "allow_exec")]
fn test_alloc_e() {
let _pages: Pages<DenyRead, DenyWrite, AllowExec> = Pages::new(256);
}
#[test]
#[cfg(feature = "allow_exec")]
fn test_alloc_re() {
let _pages: Pages<AllowRead, DenyWrite, AllowExec> = Pages::new(256);
}
#[test]
fn test_acces_rw() {
let mut pages: Pages<AllowRead, AllowWrite, DenyExec> = Pages::new(256);
for i in 0..256 {
pages[i] = i as u8;
}
for i in 0..256 {
assert_eq!(pages[i], i as u8);
}
}
#[test]
fn test_acces_r() {
let pages: Pages<AllowRead, DenyWrite, DenyExec> = Pages::new(256);
for i in 0..256 {
assert_eq!(pages[i], 0);
}
}
#[test]
#[cfg(target_arch = "x86_64")]
#[cfg(feature = "allow_exec")]
fn test_exec() {
let mut pages: Pages<AllowRead, AllowWrite, AllowExec> = Pages::new(256);
pages[0] = 0xC3;
#[cfg(target_family = "unix")]
{
pages[1] = 0x48;
pages[2] = 0x8d;
pages[3] = 0x04;
pages[4] = 0x37;
pages[5] = 0xC3;
}
#[cfg(target_family = "windows")]
{
pages[1] = 0x8d;
pages[2] = 0x04;
pages[3] = 0x11;
pages[4] = 0xC3;
}
let nop: FnRef<unsafe extern "C" fn(())> = unsafe { pages.get_fn(0) };
unsafe { nop.call(()) };
let add: FnRef<unsafe extern "C" fn(u64, u64) -> u64> = unsafe { pages.get_fn(1) };
for i in 0..256 {
for j in 0..256 {
unsafe { assert_eq!(i + j, add.call((i, j))) };
}
}
}
#[test]
fn test_allow_read() {
let pages: Pages<DenyRead, DenyWrite, DenyExec> = Pages::new(256);
let pages = pages.allow_read();
let rf: &[u8] = &pages;
}
#[test]
fn test_allow_write() {
let pages: Pages<AllowRead, DenyWrite, DenyExec> = Pages::new(256);
let mut pages = pages.allow_write();
pages[0] = 243;
assert_eq!(pages[0], 243);
}
#[test]
#[cfg(target_arch = "x86_64")]
#[cfg(feature = "allow_exec")]
fn test_allow_exec() {
let mut pages: Pages<AllowRead, AllowWrite, DenyExec> = Pages::new(256);
pages[0] = 0xC3;
#[cfg(target_family = "unix")]
{
pages[1] = 0x48;
pages[2] = 0x8d;
pages[3] = 0x04;
pages[4] = 0x37;
pages[5] = 0xC3;
}
#[cfg(target_family = "windows")]
{
pages[1] = 0x8d;
pages[2] = 0x04;
pages[3] = 0x11;
pages[4] = 0xC3;
}
let pages = pages.allow_exec().deny_write();
let nop: FnRef<unsafe extern "C" fn(())> = unsafe { pages.get_fn(0) };
unsafe { nop.call(()) };
let add: FnRef<unsafe extern "C" fn(u64, u64) -> u64> = unsafe { pages.get_fn(1) };
for i in 0..256 {
for j in 0..256 {
unsafe { assert_eq!(i + j, add.call((i, j))) };
}
}
}
}