use std::num::NonZeroUsize;
use std::{ffi, io, ptr};
#[derive(Debug)]
pub(super) struct Stack {
pub(super) pointer: *mut u8,
pub(super) length: usize,
}
impl Stack {
pub(super) fn new(guard_pages: NonZeroUsize, usable_pages: NonZeroUsize) -> io::Result<Self> {
let (guard_pages, usable_pages) = (guard_pages.get(), usable_pages.get());
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize };
assert_eq!(page_size, 4096);
let length = (guard_pages + usable_pages) * page_size;
let pointer = unsafe {
libc::mmap(
ptr::null_mut(),
length,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
-1,
0,
)
};
if pointer == libc::MAP_FAILED {
let error = io::Error::last_os_error();
return Err(error);
}
let stack = Stack {
pointer: pointer as *mut u8,
length,
};
let result = unsafe { libc::mprotect(pointer, guard_pages * page_size, libc::PROT_NONE) };
if result == -1 {
let error = io::Error::last_os_error();
return Err(error);
}
Ok(stack)
}
pub(super) fn base(&self) -> *mut u8 {
unsafe { self.pointer.add(self.length) }
}
}
impl Drop for Stack {
fn drop(&mut self) {
let result = unsafe { libc::munmap(self.pointer as *mut ffi::c_void, self.length) };
assert_eq!(result, 0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reads_and_writes() {
let stack = Stack::new(NonZeroUsize::MIN, NonZeroUsize::MIN).unwrap();
let pointer = stack.base() as *mut u8;
unsafe {
let pointer = pointer.sub(1);
pointer.write(123);
assert_eq!(pointer.read(), 123);
}
}
#[test]
fn cant_execute() {
}
#[test]
#[ignore = "aborts process"] fn overflow() {
let stack = Stack::new(NonZeroUsize::MIN, NonZeroUsize::MIN).unwrap();
let pointer = stack.base() as *mut u8;
unsafe {
let pointer = pointer.sub(4096 + 1);
pointer.write(123);
}
}
}