use std::io;
use std::ptr::NonNull;
use zeroize::Zeroize;
pub(crate) struct LockedPage {
ptr: NonNull<u8>,
len: usize,
release: unsafe fn(NonNull<u8>, usize),
}
unsafe impl Send for LockedPage {}
unsafe impl Sync for LockedPage {}
impl LockedPage {
pub(crate) fn new() -> io::Result<LockedPage> {
let len = usize::try_from(unsafe { libc::sysconf(libc::_SC_PAGESIZE) })
.map_err(|_| io::Error::other("the page size is unknown"))?;
let addr = unsafe {
libc::mmap(
std::ptr::null_mut(),
len,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
-1,
0,
)
};
if addr == libc::MAP_FAILED {
return Err(io::Error::last_os_error());
}
let ptr =
NonNull::new(addr.cast::<u8>()).ok_or_else(|| io::Error::other("null mapping"))?;
let page = LockedPage {
ptr,
len,
release: unmap,
};
if unsafe { libc::madvise(addr, len, libc::MADV_DONTDUMP) } != 0 {
return Err(io::Error::last_os_error());
}
if unsafe { libc::mlock(addr, len) } != 0 {
return Err(io::Error::last_os_error());
}
Ok(page)
}
pub(crate) fn bytes(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
pub(crate) fn bytes_mut(&mut self) -> &mut [u8] {
unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
}
}
impl Drop for LockedPage {
fn drop(&mut self) {
self.bytes_mut().zeroize();
unsafe { (self.release)(self.ptr, self.len) }
}
}
unsafe fn unmap(ptr: NonNull<u8>, len: usize) {
unsafe { libc::munmap(ptr.as_ptr().cast(), len) };
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use super::*;
thread_local! {
static RELEASED: RefCell<Option<Vec<u8>>> = const { RefCell::new(None) };
}
unsafe fn record_then_release(ptr: NonNull<u8>, len: usize) {
let bytes = unsafe { std::slice::from_raw_parts(ptr.as_ptr(), len) }.to_vec();
RELEASED.with(|r| *r.borrow_mut() = Some(bytes));
unsafe { unmap(ptr, len) }
}
fn heap_page(len: usize, release: unsafe fn(NonNull<u8>, usize)) -> LockedPage {
let bytes: Box<[u8]> = vec![0x5a; len].into_boxed_slice();
let ptr = NonNull::new(Box::into_raw(bytes).cast::<u8>()).expect("a box is not null");
LockedPage { ptr, len, release }
}
unsafe fn free_heap(ptr: NonNull<u8>, len: usize) {
let slice = std::ptr::slice_from_raw_parts_mut(ptr.as_ptr(), len);
drop(unsafe { Box::from_raw(slice) });
}
unsafe fn record_then_free(ptr: NonNull<u8>, len: usize) {
let bytes = unsafe { std::slice::from_raw_parts(ptr.as_ptr(), len) }.to_vec();
RELEASED.with(|r| *r.borrow_mut() = Some(bytes));
unsafe { free_heap(ptr, len) }
}
#[test]
fn a_page_gives_out_all_its_bytes_and_keeps_what_is_written() {
let mut page = heap_page(64, free_heap);
assert_eq!(
page.bytes(),
&[0x5a; 64][..],
"bytes gives the whole page as heap_page filled it"
);
for (i, b) in page.bytes_mut().iter_mut().enumerate() {
*b = u8::try_from(i).expect("under 256");
}
let expected: Vec<u8> = (0..64).collect();
assert_eq!(
page.bytes(),
&expected[..],
"what bytes_mut wrote reads back"
);
}
#[test]
fn a_heap_page_is_zeroed_before_it_is_released() {
let mut page = heap_page(64, record_then_free);
page.bytes_mut().fill(0xa5);
drop(page);
let released = RELEASED.with(|r| r.borrow_mut().take()).expect("released");
assert_eq!(
released,
vec![0; 64],
"drop zeroes the page before it is released"
);
}
#[test]
fn a_page_can_move_to_another_thread_and_be_shared() {
let mut page = heap_page(64, free_heap);
page = std::thread::spawn(move || {
page.bytes_mut().fill(7);
page
})
.join()
.expect("the thread ends");
let shared = std::sync::Arc::new(page);
let other = std::sync::Arc::clone(&shared);
let sum =
std::thread::spawn(move || other.bytes().iter().map(|&b| u32::from(b)).sum::<u32>())
.join()
.expect("the thread ends");
assert_eq!(
sum,
7 * 64,
"the other thread reads the 64 sevens written here"
);
assert_eq!(
shared.bytes(),
&[7; 64][..],
"sharing the page changes none of its bytes"
);
}
#[test]
#[cfg_attr(miri, ignore = "Miri does not support madvise with MADV_DONTDUMP")]
fn a_page_is_zeroed_before_it_is_released() {
let mut page = LockedPage::new().expect("page");
page.bytes_mut().fill(0xa5);
let len = page.bytes().len();
page.release = record_then_release;
drop(page);
let released = RELEASED.with(|r| r.borrow_mut().take()).expect("released");
assert_eq!(
released,
vec![0; len],
"drop zeroes the mapped page before it is unmapped"
);
}
}