From e2df751eeafca70b9cbcb7de8e93c0a74581dcbc Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Mon, 2 May 2022 13:56:31 +0200 Subject: [PATCH 01/46] feat: memory allocation strategies --- engine/runtime/Cargo.toml | 3 + engine/runtime/src/lib.rs | 3 + engine/runtime/src/memories/frag.rs | 292 ++++++++++++++++++++++++++++ engine/runtime/src/memories/mod.rs | 1 + engine/runtime/tests/alloc_tests.rs | 59 ++++++ 5 files changed, 358 insertions(+) create mode 100644 engine/runtime/src/memories/frag.rs create mode 100644 engine/runtime/tests/alloc_tests.rs diff --git a/engine/runtime/Cargo.toml b/engine/runtime/Cargo.toml index 1a4f10358..830d8baea 100644 --- a/engine/runtime/Cargo.toml +++ b/engine/runtime/Cargo.toml @@ -16,5 +16,8 @@ dirs = { version = "4.0.0" } thiserror = { version = "1.0" } iota-crypto = { version = "0.8.0", features = ["sha"] } +# experimental dependencies for fragmenting allocator +libc = { version = "0.2" } + [dev-dependencies] serde_json = { version = "1.0" } \ No newline at end of file diff --git a/engine/runtime/src/lib.rs b/engine/runtime/src/lib.rs index 7a2afc61f..d3f44a483 100644 --- a/engine/runtime/src/lib.rs +++ b/engine/runtime/src/lib.rs @@ -36,6 +36,9 @@ pub enum MemoryError { #[error("Illegal zero-sized value provided")] ZeroSizedNotAllowed, + + #[error("Failed to allocate memory ({0})")] + Allocation(String), } /// A simple trait to force the types to call `zeroize()` when dropping diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs new file mode 100644 index 000000000..515d2db33 --- /dev/null +++ b/engine/runtime/src/memories/frag.rs @@ -0,0 +1,292 @@ +// Copyright 2020-2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! This module implements a memory allocator, that fragments two or more parts +//! of requested memory. There are some strategies to fragment multiple parts of memory. +//! The most simple approach is to allocate memory multiple times, return the final allocation +//! as the desired memory. +//! +//! Allocators differ between operating systems. On *nix and BSD-based systems `malloc(int)` is being called +//! but does not initialize the allocated spaced. Microsoft Windows uses +//! [`VirtualAlloc`](https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc) +//! allocates and initializes a region of memory pages with zeroes. +//! +//! [`FragStrategy`] implements at least two possible allocation strategies: +//! +//! - Default: The algorithm tries to allocate a huge amount of memory space while keeping a certain address distance +//! - Memory Mapped: anonymous memory is being mapping, the memory address will be randomly selected. + +use crate::MemoryError; +use std::{fmt::Debug, mem::MaybeUninit, ptr::NonNull}; + +/// Fragmenting strategy to allocate memory at random addresses. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum FragStrategy { + /// Anonymously maps a region of memory + MMap, + + /// System's allocator will be called a few times + Default, +} + +// ----------------------------------------------------------------------------- + +/// Custom allocator trait +pub trait Alloc { + type Error; + + /// Allocates `T`, returns an error if something wrong happened + fn alloc() -> Result, Self::Error>; +} + +// ----------------------------------------------------------------------------- + +/// Frag is being used as control object to load different allocators +/// according to their strategy +pub struct Frag; + +impl Frag { + /// Returns a fragmenting allocator by strategy + /// + /// # Example + /// + /// ```skip + /// use stronghold_engine::runtime::memories::*; + /// + /// let allocator = Frag::by_strategy(FragStrategy::Fork); + /// ``` + pub fn alloc(s: FragStrategy) -> Result, MemoryError> + where + T: Default, + { + match s { + FragStrategy::Default => DefaultAlloc::alloc(), + FragStrategy::MMap => MemoryMapAlloc::alloc(), + // FragStrategy is non_exhaustive? + // _ => Err(MemoryError::Allocation( + // "Allocator strategy not implemented!".to_owned(), + // )), + } + } +} + +// ----------------------------------------------------------------------------- + +#[derive(Default, Clone)] +struct ForkAlloc; + +impl Alloc for ForkAlloc +where + T: Default, +{ + type Error = MemoryError; + + #[cfg(target_os = "windows")] + fn alloc() -> Result { + todo!() + } + + #[cfg(any(target_os = "linux", target_os = "unix"))] + fn alloc() -> Result, Self::Error> { + let mut pipe = [-1_i32; 2]; + let piped_result = unsafe { libc::pipe(&mut pipe as *mut i32) }; + + if piped_result < 0 { + return Err(MemoryError::Allocation("Failed to create pipe".to_string())); + } + + // child process + let pid; + unsafe { + pid = libc::fork(); + + match pid { + 0 => { + std::panic::set_hook(Box::new(|_| { + libc::exit(0); + })); + + // ptrace hook + // libc::ptrace(libc::PTRACE_TRACEME); + + // todo: register error hooks + // allocate memory and free it immediately + // (0..10).for_each(|_| { + MaybeUninit::<[u8; usize::MAX >> 45]>::uninit().as_mut_ptr(); + // }); + + let mut ptr = libc::malloc(usize::MAX); + let mut i: usize = 0; + while ptr.is_null() { + ptr = libc::malloc(usize::MAX >> i); + i = i.saturating_add(1); + } + + println!("a got allocated at {:p}", ptr); + + let mut ptr = libc::malloc(usize::MAX); + let mut i: usize = 0; + while ptr.is_null() { + ptr = libc::malloc(usize::MAX >> i); + i = i.saturating_add(1); + } + println!("b got allocated at {:p}", ptr); + libc::free(ptr); + + libc::dup2(pipe[1], 1); // map write to stdout + libc::close(pipe[0]); + + let mut object = T::default(); + let ptr = &mut object as *mut T as *mut u8; + let mut size = std::mem::size_of::(); + + while size > 0 { + let w = libc::write(1, ptr as *const libc::c_void, size); + if w < 0 { + return Err(MemoryError::Allocation("Failed to send allocated memory".to_string())); + } + + size -= w as usize; + } + + libc::_exit(0); + } + _ if pid < 0 => { + return Err(MemoryError::Allocation("Failed to fork process".to_string())); + } + + _ => { + println!("Got child pid: {}", pid); + } + }; + } + + unsafe { + let mut status = 0; + libc::waitpid(pid, &mut status, 0); + + match status { + _ if libc::WIFEXITED(status) => { + if libc::WIFSTOPPED(status) { + return Err(MemoryError::Allocation("Child process crash".to_string())); + } + + // read from pipe + let mut m: MaybeUninit = MaybeUninit::uninit(); + let mut size = std::mem::size_of::(); + let p = m.as_mut_ptr(); + println!("Reading from pipe"); + while size > 0 { + let read = libc::read(pipe[0], p as *mut libc::c_void, size); + if read < 0 { + return Err(MemoryError::Allocation("Failed to read from pipe".to_string())); + } + + size -= read as usize; + } + + Ok(Box::from_raw(p)) + } + _ => Err(MemoryError::Allocation( + "Unknown state while waiting for child process".to_string(), + )), + } + } + } +} + +// ----------------------------------------------------------------------------- + +#[derive(Default, Clone)] +struct MemoryMapAlloc; + +impl Alloc for MemoryMapAlloc +where + T: Default, +{ + type Error = MemoryError; + + #[cfg(any(target_os = "linux", target_os = "unix"))] + fn alloc() -> Result, Self::Error> { + let length = std::mem::size_of::(); + + unsafe { + loop { + let mut addr: usize = random::random(); + + let ptr = libc::mmap( + &mut addr as *mut usize as *mut libc::c_void, + length, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + 0, + 0, + ); + + if !ptr.is_null() { + let nn_ptr: NonNull = NonNull::new(ptr as *mut _).expect("Failed to wrap raw pointer"); + let t = T::default(); + nn_ptr.as_ptr().write(t); + + return Ok(Box::from_raw(nn_ptr.as_ptr())); + } + } + } + } + + #[cfg(target_os = "windows")] + fn alloc() -> Result { + todo!() + } +} + +// ----------------------------------------------------------------------------- + +/// [`DefaultAlloc`] tries to allocate a "huge" block of memory randomly, +/// resizing the chunk to the desired size of the to be allocated object. +/// +/// The actual implementation is system dependent and might vary. +/// +/// # Example +/// ``` +/// use stronghold_engine::runtime::memories::frag::{Frag, FragStrategy}; +/// +/// // allocates the object at a random address +/// let object = Frag::alloc().unwrap(); +/// ``` +#[derive(Default, Clone)] +struct DefaultAlloc; + +impl Alloc for DefaultAlloc +where + T: Default, +{ + type Error = MemoryError; + + #[cfg(any(target_os = "linux", target_os = "unix"))] + fn alloc() -> Result, Self::Error> { + use random::{thread_rng, Rng}; + + let mut rng = thread_rng(); + loop { + unsafe { + let alloc_size = rng.gen::() >> 32; + let mem_ptr = libc::malloc(alloc_size); + if mem_ptr.is_null() { + continue; + } + let actual_size = std::mem::size_of::(); + let actual_mem = libc::realloc(mem_ptr, actual_size) as *mut T; + actual_mem.write(T::default()); + + return Ok(Box::from_raw(actual_mem)); + } + } + } + + #[cfg(target_os = "windows")] + fn alloc() -> Result { + todo!() + } +} diff --git a/engine/runtime/src/memories/mod.rs b/engine/runtime/src/memories/mod.rs index 9fef91159..c6df2c6da 100644 --- a/engine/runtime/src/memories/mod.rs +++ b/engine/runtime/src/memories/mod.rs @@ -3,5 +3,6 @@ pub mod buffer; pub mod file_memory; +pub mod frag; pub mod noncontiguous_memory; pub mod ram_memory; diff --git a/engine/runtime/tests/alloc_tests.rs b/engine/runtime/tests/alloc_tests.rs new file mode 100644 index 000000000..b00561f16 --- /dev/null +++ b/engine/runtime/tests/alloc_tests.rs @@ -0,0 +1,59 @@ +// Copyright 2020-2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use runtime::{ + memories::frag::{Frag, FragStrategy}, + MemoryError, +}; + +#[derive(PartialEq, Debug)] +struct TestStruct { + id: usize, + name: String, +} + +impl Default for TestStruct { + fn default() -> Self { + Self { + id: 123456789, + name: "Some heap allocated value".to_owned(), + } + } +} + +#[test] +fn test_allocate_default() -> Result<(), MemoryError> { + test_allocate(FragStrategy::Default) +} + +#[test] +fn test_allocate_map() -> Result<(), MemoryError> { + test_allocate(FragStrategy::MMap) +} + +fn test_allocate(strategy: FragStrategy) -> Result<(), MemoryError> { + let runs = 100; + + for _ in 0..runs { + let result = Frag::alloc::(strategy.clone()); + assert!(result.is_ok()); + assert_eq!(*result.unwrap(), TestStruct::default()); + + let a = Frag::alloc::(strategy.clone())?; + let b = Frag::alloc::(strategy.clone())?; + + assert!(distance(&*a, &*b) > 0xFFFF); + } + + Ok(()) +} + +// ---------------------------------------------------------------------------- + +/// Calculates the distance between two pointers +fn distance(a: &T, b: &T) -> usize { + let a = a as *const T as usize; + let b = b as *const T as usize; + + a.abs_diff(b) +} From 8fff74ffc2cf1cc5534bc22708773b8e741d0c29 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Mon, 2 May 2022 17:40:01 +0200 Subject: [PATCH 02/46] fix: remove unused imports --- engine/runtime/src/memories/frag.rs | 31 ++++++++++++++--------------- engine/runtime/tests/alloc_tests.rs | 6 +++--- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 515d2db33..1bdc056e5 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -17,7 +17,7 @@ //! - Memory Mapped: anonymous memory is being mapping, the memory address will be randomly selected. use crate::MemoryError; -use std::{fmt::Debug, mem::MaybeUninit, ptr::NonNull}; +use std::{fmt::Debug, mem::MaybeUninit}; /// Fragmenting strategy to allocate memory at random addresses. #[derive(Debug, Clone)] @@ -54,7 +54,7 @@ impl Frag { /// ```skip /// use stronghold_engine::runtime::memories::*; /// - /// let allocator = Frag::by_strategy(FragStrategy::Fork); + /// let object = Frag::by_strategy(FragStrategy::Default).unwrap(); /// ``` pub fn alloc(s: FragStrategy) -> Result, MemoryError> where @@ -63,10 +63,6 @@ impl Frag { match s { FragStrategy::Default => DefaultAlloc::alloc(), FragStrategy::MMap => MemoryMapAlloc::alloc(), - // FragStrategy is non_exhaustive? - // _ => Err(MemoryError::Allocation( - // "Allocator strategy not implemented!".to_owned(), - // )), } } } @@ -113,7 +109,7 @@ where // todo: register error hooks // allocate memory and free it immediately // (0..10).for_each(|_| { - MaybeUninit::<[u8; usize::MAX >> 45]>::uninit().as_mut_ptr(); + // MaybeUninit::<[u8; usize::MAX >> 45]>::uninit().as_mut_ptr(); // }); let mut ptr = libc::malloc(usize::MAX); @@ -209,27 +205,30 @@ where #[cfg(any(target_os = "linux", target_os = "unix"))] fn alloc() -> Result, Self::Error> { - let length = std::mem::size_of::(); + let size = std::mem::size_of::(); + + use random::{thread_rng, Rng}; + let mut rng = thread_rng(); unsafe { loop { - let mut addr: usize = random::random(); + let mut addr: usize = rng.gen::() >> 48; let ptr = libc::mmap( &mut addr as *mut usize as *mut libc::c_void, - length, + size * 2, libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, - 0, + libc::MAP_ANONYMOUS | libc::MAP_SHARED, + -1, 0, - ); + ) as *mut T; if !ptr.is_null() { - let nn_ptr: NonNull = NonNull::new(ptr as *mut _).expect("Failed to wrap raw pointer"); + // libc::realloc(ptr as *mut libc::c_void, size); let t = T::default(); - nn_ptr.as_ptr().write(t); + ptr.write(t); - return Ok(Box::from_raw(nn_ptr.as_ptr())); + return Ok(Box::from_raw(ptr)); } } } diff --git a/engine/runtime/tests/alloc_tests.rs b/engine/runtime/tests/alloc_tests.rs index b00561f16..69896ad64 100644 --- a/engine/runtime/tests/alloc_tests.rs +++ b/engine/runtime/tests/alloc_tests.rs @@ -37,12 +37,12 @@ fn test_allocate(strategy: FragStrategy) -> Result<(), MemoryError> { for _ in 0..runs { let result = Frag::alloc::(strategy.clone()); assert!(result.is_ok()); - assert_eq!(*result.unwrap(), TestStruct::default()); + // assert_eq!(&*result.unwrap(), &TestStruct::default()); let a = Frag::alloc::(strategy.clone())?; let b = Frag::alloc::(strategy.clone())?; - - assert!(distance(&*a, &*b) > 0xFFFF); + let distance = distance(&*a, &*b); + assert!(distance > 0xFFFF, "Illegal distance {}", distance); } Ok(()) From 440af86434617607d14f68ce42986a4529b6b5e4 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Tue, 3 May 2022 11:22:44 +0200 Subject: [PATCH 03/46] feat: memory mapped allocation --- engine/runtime/src/memories/frag.rs | 84 +++++++++++++++++++++-------- engine/runtime/tests/alloc_tests.rs | 46 +++++++++++----- 2 files changed, 93 insertions(+), 37 deletions(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 1bdc056e5..828708f4c 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -17,17 +17,17 @@ //! - Memory Mapped: anonymous memory is being mapping, the memory address will be randomly selected. use crate::MemoryError; -use std::{fmt::Debug, mem::MaybeUninit}; +use std::{fmt::Debug, mem::MaybeUninit, ptr::NonNull}; /// Fragmenting strategy to allocate memory at random addresses. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] #[non_exhaustive] pub enum FragStrategy { /// Anonymously maps a region of memory MMap, /// System's allocator will be called a few times - Default, + Direct, } // ----------------------------------------------------------------------------- @@ -37,7 +37,7 @@ pub trait Alloc { type Error; /// Allocates `T`, returns an error if something wrong happened - fn alloc() -> Result, Self::Error>; + fn alloc() -> Result, Self::Error>; } // ----------------------------------------------------------------------------- @@ -56,15 +56,45 @@ impl Frag { /// /// let object = Frag::by_strategy(FragStrategy::Default).unwrap(); /// ``` - pub fn alloc(s: FragStrategy) -> Result, MemoryError> + fn alloc_single(s: FragStrategy) -> Result, MemoryError> where T: Default, { match s { - FragStrategy::Default => DefaultAlloc::alloc(), + FragStrategy::Direct => DirectAlloc::alloc(), FragStrategy::MMap => MemoryMapAlloc::alloc(), } } + + /// Tries to allocate two objects of the same type with a minimum distance in memory space. + pub fn alloc2(strategy: FragStrategy, distance: usize) -> Option<(NonNull, NonNull)> + where + T: Default, + { + let d = |a: &T, b: &T| { + let a = a as *const T as usize; + let b = b as *const T as usize; + + a.abs_diff(b) + }; + + let a = Self::alloc_single::(strategy).ok()?; + let b = Self::alloc_single::(strategy).ok()?; + unsafe { + if d(a.as_ref(), b.as_ref()) < distance { + return None; + } + } + + Some((a, b)) + } + /// Tries to allocate two objects of the same type with a default minimum distance in memory space of `0xFFFF`. + pub fn alloc(strategy: FragStrategy) -> Option<(NonNull, NonNull)> + where + T: Default, + { + Self::alloc2(strategy, 0xFFFF) + } } // ----------------------------------------------------------------------------- @@ -83,8 +113,8 @@ where todo!() } - #[cfg(any(target_os = "linux", target_os = "unix"))] - fn alloc() -> Result, Self::Error> { + #[cfg(any(target_os = "linux", target_os = "macos"))] + fn alloc() -> Result, Self::Error> { let mut pipe = [-1_i32; 2]; let piped_result = unsafe { libc::pipe(&mut pipe as *mut i32) }; @@ -182,7 +212,7 @@ where size -= read as usize; } - Ok(Box::from_raw(p)) + Ok(NonNull::new_unchecked(p)) } _ => Err(MemoryError::Allocation( "Unknown state while waiting for child process".to_string(), @@ -203,8 +233,8 @@ where { type Error = MemoryError; - #[cfg(any(target_os = "linux", target_os = "unix"))] - fn alloc() -> Result, Self::Error> { + #[cfg(any(target_os = "linux", target_os = "macos"))] + fn alloc() -> Result, Self::Error> { let size = std::mem::size_of::(); use random::{thread_rng, Rng}; @@ -212,23 +242,31 @@ where unsafe { loop { - let mut addr: usize = rng.gen::() >> 48; + let mut addr: usize = rng.gen::(); let ptr = libc::mmap( &mut addr as *mut usize as *mut libc::c_void, - size * 2, + size, libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_ANONYMOUS | libc::MAP_SHARED, + libc::MAP_ANONYMOUS | libc::MAP_PRIVATE, -1, 0, - ) as *mut T; + ); + + if ptr == libc::MAP_FAILED { + continue; + } + + // on linux this isn't required to commit memory + // libc::madvise(&mut addr as *mut usize as *mut libc::c_void, size, libc::MADV_WILLNEED); + + let ptr = ptr as *mut T; if !ptr.is_null() { - // libc::realloc(ptr as *mut libc::c_void, size); let t = T::default(); ptr.write(t); - return Ok(Box::from_raw(ptr)); + return Ok(NonNull::new_unchecked(ptr)); } } } @@ -242,7 +280,7 @@ where // ----------------------------------------------------------------------------- -/// [`DefaultAlloc`] tries to allocate a "huge" block of memory randomly, +/// [`DirectAlloc`] tries to allocate a "huge" block of memory randomly, /// resizing the chunk to the desired size of the to be allocated object. /// /// The actual implementation is system dependent and might vary. @@ -255,16 +293,16 @@ where /// let object = Frag::alloc().unwrap(); /// ``` #[derive(Default, Clone)] -struct DefaultAlloc; +struct DirectAlloc; -impl Alloc for DefaultAlloc +impl Alloc for DirectAlloc where T: Default, { type Error = MemoryError; - #[cfg(any(target_os = "linux", target_os = "unix"))] - fn alloc() -> Result, Self::Error> { + #[cfg(any(target_os = "linux", target_os = "macos"))] + fn alloc() -> Result, Self::Error> { use random::{thread_rng, Rng}; let mut rng = thread_rng(); @@ -279,7 +317,7 @@ where let actual_mem = libc::realloc(mem_ptr, actual_size) as *mut T; actual_mem.write(T::default()); - return Ok(Box::from_raw(actual_mem)); + return Ok(NonNull::new_unchecked(actual_mem)); } } } diff --git a/engine/runtime/tests/alloc_tests.rs b/engine/runtime/tests/alloc_tests.rs index 69896ad64..8040c72a2 100644 --- a/engine/runtime/tests/alloc_tests.rs +++ b/engine/runtime/tests/alloc_tests.rs @@ -6,7 +6,7 @@ use runtime::{ MemoryError, }; -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone)] struct TestStruct { id: usize, name: String, @@ -22,27 +22,45 @@ impl Default for TestStruct { } #[test] -fn test_allocate_default() -> Result<(), MemoryError> { - test_allocate(FragStrategy::Default) +fn test_allocate_direct() { + assert!(test_allocate(FragStrategy::Direct).is_ok()); + assert!(test_allocate2(FragStrategy::Direct).is_ok()); } #[test] -fn test_allocate_map() -> Result<(), MemoryError> { - test_allocate(FragStrategy::MMap) +fn test_allocate_map() { + assert!(test_allocate(FragStrategy::MMap).is_ok()); + assert!(test_allocate2(FragStrategy::MMap).is_ok()); +} + +fn test_allocate2(strategy: FragStrategy) -> Result<(), MemoryError> { + loop { + unsafe { + match Frag::alloc2::(strategy, 0xFFFF) { + Some((a, b)) => { + assert!(distance(a.as_ref(), b.as_ref()) > 0xFFFF); + break; + } + None => continue, + } + } + } + + Ok(()) } fn test_allocate(strategy: FragStrategy) -> Result<(), MemoryError> { let runs = 100; - for _ in 0..runs { - let result = Frag::alloc::(strategy.clone()); - assert!(result.is_ok()); - // assert_eq!(&*result.unwrap(), &TestStruct::default()); - - let a = Frag::alloc::(strategy.clone())?; - let b = Frag::alloc::(strategy.clone())?; - let distance = distance(&*a, &*b); - assert!(distance > 0xFFFF, "Illegal distance {}", distance); + unsafe { + match Frag::alloc::(strategy) { + Some((a, b)) => { + assert!(distance(a.as_ref(), b.as_ref()) > 0xFFFF); + break; + } + None => continue, + } + } } Ok(()) From 7c42de68684f0023d4678670bb11dea56ca425a5 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Tue, 3 May 2022 14:20:55 +0200 Subject: [PATCH 04/46] fix: wrong reference to crate in doctest --- engine/runtime/Cargo.toml | 8 +++++--- engine/runtime/src/memories/frag.rs | 21 +++++++++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/engine/runtime/Cargo.toml b/engine/runtime/Cargo.toml index 830d8baea..17d571bf3 100644 --- a/engine/runtime/Cargo.toml +++ b/engine/runtime/Cargo.toml @@ -15,9 +15,11 @@ random = { version = "0.8.4", package = "rand" } dirs = { version = "4.0.0" } thiserror = { version = "1.0" } iota-crypto = { version = "0.8.0", features = ["sha"] } - -# experimental dependencies for fragmenting allocator libc = { version = "0.2" } +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3.9" } +kernel32-sys = { version = "0.2.2" } + [dev-dependencies] -serde_json = { version = "1.0" } \ No newline at end of file +serde_json = { version = "1.0" } diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 828708f4c..d5c710465 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -16,6 +16,11 @@ //! - Default: The algorithm tries to allocate a huge amount of memory space while keeping a certain address distance //! - Memory Mapped: anonymous memory is being mapping, the memory address will be randomly selected. +#[cfg(windows)] +extern crate kernel32; +#[cfg(windows)] +extern crate winapi; + use crate::MemoryError; use std::{fmt::Debug, mem::MaybeUninit, ptr::NonNull}; @@ -258,7 +263,8 @@ where } // on linux this isn't required to commit memory - // libc::madvise(&mut addr as *mut usize as *mut libc::c_void, size, libc::MADV_WILLNEED); + #[cfg(any(target_os = "macos"))] + libc::madvise(&mut addr as *mut usize as *mut libc::c_void, size, libc::MADV_WILLNEED); let ptr = ptr as *mut T; @@ -287,10 +293,10 @@ where /// /// # Example /// ``` -/// use stronghold_engine::runtime::memories::frag::{Frag, FragStrategy}; +/// use runtime::memories::frag::{Frag, FragStrategy}; /// /// // allocates the object at a random address -/// let object = Frag::alloc().unwrap(); +/// let object = Frag::alloc::(FragStrategy::Direct).unwrap(); /// ``` #[derive(Default, Clone)] struct DirectAlloc; @@ -324,6 +330,13 @@ where #[cfg(target_os = "windows")] fn alloc() -> Result { - todo!() + use random::{thread_rng, Rng}; + + let mut rng = thread_rng(); + loop { + unsafe { + winapi::VirtualAlloc(); + }; + } } } From 76e3876ebe49c952ed5352fc7d46364bb340e838 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Thu, 5 May 2022 11:47:30 +0200 Subject: [PATCH 05/46] feat: allocating memory under windows --- engine/benches/benchmark.rs | 51 ++++++++- engine/runtime/Cargo.toml | 5 +- engine/runtime/src/memories/frag.rs | 166 +++++++++++++++++++++++++--- engine/runtime/tests/alloc_tests.rs | 49 +++----- 4 files changed, 219 insertions(+), 52 deletions(-) diff --git a/engine/benches/benchmark.rs b/engine/benches/benchmark.rs index f15a12451..6a5144164 100644 --- a/engine/benches/benchmark.rs +++ b/engine/benches/benchmark.rs @@ -11,6 +11,7 @@ use engine::{ store::Cache, vault::{DbView, Key, RecordHint, RecordId, VaultId}, }; +use runtime::memories::frag::{Frag, FragStrategy}; use crate::provider::Provider; @@ -107,6 +108,52 @@ fn bench_store_decompress(c: &mut Criterion) { }); } +fn bench_allocate_direct(c: &mut Criterion) { + #[allow(dead_code)] + struct TestStruct { + id: usize, + name: String, + } + + impl Default for TestStruct { + fn default() -> Self { + Self { + id: 0xFFFF_FFFF_FFFF_FFFF, + name: "Some TestingStruct".to_owned(), + } + } + } + + c.bench_function("Allocate memory direct", |b| { + b.iter(|| { + Frag::alloc::(FragStrategy::Direct); + }); + }); +} + +fn bench_allocate_mapped(c: &mut Criterion) { + #[allow(dead_code)] + struct TestStruct { + id: usize, + name: String, + } + + impl Default for TestStruct { + fn default() -> Self { + Self { + id: 0xFFFF_FFFF_FFFF_FFFF, + name: "Some TestingStruct".to_owned(), + } + } + } + + c.bench_function("Allocate memory mapped", |b| { + b.iter(|| { + Frag::alloc::(FragStrategy::Map); + }); + }); +} + criterion_group!( benches, bench_snapshot_compression, @@ -115,6 +162,8 @@ criterion_group!( bench_store_compress, bench_store_compression, bench_store_decompress, - bench_vault_write + bench_vault_write, + bench_allocate_direct, + bench_allocate_mapped ); criterion_main!(benches); diff --git a/engine/runtime/Cargo.toml b/engine/runtime/Cargo.toml index 17d571bf3..c6f944916 100644 --- a/engine/runtime/Cargo.toml +++ b/engine/runtime/Cargo.toml @@ -18,8 +18,7 @@ iota-crypto = { version = "0.8.0", features = ["sha"] } libc = { version = "0.2" } [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.9" } -kernel32-sys = { version = "0.2.2" } +windows = { version = "0.36.0", features = ["Win32_System_Memory", "Win32_System_SystemInformation", "Win32_System_Diagnostics_Debug", "Win32_Foundation", "Win32_Security"] } [dev-dependencies] -serde_json = { version = "1.0" } +serde_json = { version = "1.0" } \ No newline at end of file diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index d5c710465..6df44bab2 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -16,20 +16,15 @@ //! - Default: The algorithm tries to allocate a huge amount of memory space while keeping a certain address distance //! - Memory Mapped: anonymous memory is being mapping, the memory address will be randomly selected. -#[cfg(windows)] -extern crate kernel32; -#[cfg(windows)] -extern crate winapi; - use crate::MemoryError; -use std::{fmt::Debug, mem::MaybeUninit, ptr::NonNull}; +use std::{fmt::Debug, ptr::NonNull}; /// Fragmenting strategy to allocate memory at random addresses. #[derive(Debug, Clone, Copy)] #[non_exhaustive] pub enum FragStrategy { /// Anonymously maps a region of memory - MMap, + Map, /// System's allocator will be called a few times Direct, @@ -61,13 +56,13 @@ impl Frag { /// /// let object = Frag::by_strategy(FragStrategy::Default).unwrap(); /// ``` - fn alloc_single(s: FragStrategy) -> Result, MemoryError> + pub fn alloc_single(s: FragStrategy) -> Result, MemoryError> where T: Default, { match s { FragStrategy::Direct => DirectAlloc::alloc(), - FragStrategy::MMap => MemoryMapAlloc::alloc(), + FragStrategy::Map => MemoryMapAlloc::alloc(), } } @@ -114,12 +109,14 @@ where type Error = MemoryError; #[cfg(target_os = "windows")] - fn alloc() -> Result { + fn alloc() -> Result, Self::Error> { todo!() } #[cfg(any(target_os = "linux", target_os = "macos"))] fn alloc() -> Result, Self::Error> { + use std::mem::MaybeUninit; + let mut pipe = [-1_i32; 2]; let piped_result = unsafe { libc::pipe(&mut pipe as *mut i32) }; @@ -279,8 +276,78 @@ where } #[cfg(target_os = "windows")] - fn alloc() -> Result { - todo!() + fn alloc() -> Result, Self::Error> { + use random::{thread_rng, Rng}; + let mut rng = thread_rng(); + + let handle = windows::Win32::Foundation::INVALID_HANDLE_VALUE; + + unsafe { + // allocation prelude + { + let r_addr = rng.gen::() >> 4; + + let random_mapping = windows::Win32::System::Memory::CreateFileMappingW( + handle, + std::ptr::null_mut(), + windows::Win32::System::Memory::PAGE_READWRITE, + 0, + r_addr, + windows::core::PCWSTR(std::ptr::null_mut()), + ) + .map_err(|e| MemoryError::Allocation(e.to_string()))?; + + if let Err(e) = last_error() { + return Err(e); + } + + let _ = windows::Win32::System::Memory::MapViewOfFile( + random_mapping, + windows::Win32::System::Memory::FILE_MAP_ALL_ACCESS, + 0, + 0, + r_addr as usize, + ); + + if let Err(e) = last_error() { + return Err(e); + } + } + + // actual memory mapping + { + let actual_size = std::mem::size_of::() as u32; + let actual_mapping = windows::Win32::System::Memory::CreateFileMappingW( + handle, + std::ptr::null_mut(), + windows::Win32::System::Memory::PAGE_READWRITE, + 0, + actual_size, + windows::core::PCWSTR(std::ptr::null_mut()), + ) + .map_err(|e| MemoryError::Allocation(e.to_string()))?; + + if let Err(e) = last_error() { + return Err(e); + } + + let actual_mem = windows::Win32::System::Memory::MapViewOfFile( + actual_mapping, + windows::Win32::System::Memory::FILE_MAP_ALL_ACCESS, + 0, + 0, + actual_size as usize, + ) as *mut T; + + if let Err(e) = last_error() { + return Err(e); + } + + actual_mem.write(T::default()); + + return Ok(NonNull::new_unchecked(actual_mem as *mut T)); + } + } } } @@ -329,14 +396,79 @@ where } #[cfg(target_os = "windows")] - fn alloc() -> Result { - use random::{thread_rng, Rng}; - - let mut rng = thread_rng(); + fn alloc() -> Result, Self::Error> { loop { unsafe { - winapi::VirtualAlloc(); + let actual_size = std::mem::size_of::(); + + let actual_mem = windows::Win32::System::Memory::VirtualAlloc( + std::ptr::null_mut(), + actual_size, + windows::Win32::System::Memory::MEM_COMMIT | windows::Win32::System::Memory::MEM_RESERVE, + windows::Win32::System::Memory::PAGE_READWRITE, + ); + + if actual_mem.is_null() { + if let Err(_) = last_error() { + continue; + } + } + + let actual_mem = actual_mem as *mut T; + actual_mem.write(T::default()); + return Ok(NonNull::new_unchecked(actual_mem)); }; } } } + +// ----------------------------------------------------------------------------- + +/// Rounds `value` up to a multiple of `base` +/// +/// # Example +/// ``` +/// let n = 13; +/// let b = 14; +/// let c = runtime::memories::frag::round_up(n, b); +/// assert_eq!(c, 14); +/// ``` +pub fn round_up(value: usize, base: usize) -> usize { + if base == 0 { + return value; + } + + match value % base { + 0 => value, + remainder => value + base - remainder, + } +} + +/// Checks for error codes under Windows. +/// +/// Detected errors will be returned as [`MemoryError`]. If no error has been +/// detected eg. the function result is `0`, `Ok` will be returned. +#[cfg(target_os = "windows")] +fn last_error() -> Result<(), MemoryError> { + unsafe { + match windows::Win32::Foundation::GetLastError() { + windows::Win32::Foundation::WIN32_ERROR(0) => Ok(()), + windows::Win32::Foundation::ERROR_ALREADY_EXISTS => { + Err(MemoryError::Allocation("Mapping already exists".to_owned())) + } + windows::Win32::Foundation::ERROR_INVALID_HANDLE => { + Err(MemoryError::Allocation("Invalid handle for mapped memory".to_owned())) + } + windows::Win32::Foundation::ERROR_COMMITMENT_LIMIT => Err(MemoryError::Allocation( + "The paging file is too small for this operation to complete".to_owned(), + )), + windows::Win32::Foundation::ERROR_INVALID_PARAMETER => { + Err(MemoryError::Allocation("The parameter is incorrect.".to_owned())) + } + windows::Win32::Foundation::ERROR_INVALID_ADDRESS => { + Err(MemoryError::Allocation("Attempt to access invalid address.".to_owned())) + } + err => Err(MemoryError::Allocation(format!("Unknown error code 0x{:08x}", err.0))), + } + } +} diff --git a/engine/runtime/tests/alloc_tests.rs b/engine/runtime/tests/alloc_tests.rs index 8040c72a2..8d5fcd03e 100644 --- a/engine/runtime/tests/alloc_tests.rs +++ b/engine/runtime/tests/alloc_tests.rs @@ -1,6 +1,8 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use std::ptr::NonNull; + use runtime::{ memories::frag::{Frag, FragStrategy}, MemoryError, @@ -21,46 +23,31 @@ impl Default for TestStruct { } } +/// this fails under windows #[test] fn test_allocate_direct() { - assert!(test_allocate(FragStrategy::Direct).is_ok()); - assert!(test_allocate2(FragStrategy::Direct).is_ok()); + assert!(test_allocate::(|| Frag::alloc(FragStrategy::Direct)).is_ok()); + assert!(test_allocate::(|| Frag::alloc2(FragStrategy::Direct, 0xFFFF)).is_ok()); } #[test] fn test_allocate_map() { - assert!(test_allocate(FragStrategy::MMap).is_ok()); - assert!(test_allocate2(FragStrategy::MMap).is_ok()); + assert!(test_allocate::(|| Frag::alloc(FragStrategy::Map)).is_ok()); + assert!(test_allocate::(|| Frag::alloc2(FragStrategy::Map, 0xFFFF)).is_ok()); } -fn test_allocate2(strategy: FragStrategy) -> Result<(), MemoryError> { - loop { - unsafe { - match Frag::alloc2::(strategy, 0xFFFF) { - Some((a, b)) => { - assert!(distance(a.as_ref(), b.as_ref()) > 0xFFFF); - break; - } - None => continue, - } - } - } +fn test_allocate(allocator: F) -> Result<(), MemoryError> +where + T: Default, + F: Fn() -> Option<(NonNull, NonNull)>, +{ + let min_distance = 0xFFFF; + let result = allocator(); + assert!(result.is_some()); + let (a, b) = result.unwrap(); - Ok(()) -} - -fn test_allocate(strategy: FragStrategy) -> Result<(), MemoryError> { - let runs = 100; - for _ in 0..runs { - unsafe { - match Frag::alloc::(strategy) { - Some((a, b)) => { - assert!(distance(a.as_ref(), b.as_ref()) > 0xFFFF); - break; - } - None => continue, - } - } + unsafe { + assert!(distance(a.as_ref(), b.as_ref()) > min_distance); } Ok(()) From 2c9f8bcab52a20b9cca3058eb6594d1f7d5e9846 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Thu, 5 May 2022 15:16:35 +0200 Subject: [PATCH 06/46] fix: allocate larger region of memory to ensure distance --- engine/runtime/Cargo.toml | 3 + engine/runtime/src/memories/frag.rs | 138 ++-------------------------- engine/runtime/tests/alloc_tests.rs | 12 ++- 3 files changed, 19 insertions(+), 134 deletions(-) diff --git a/engine/runtime/Cargo.toml b/engine/runtime/Cargo.toml index c6f944916..3004b6099 100644 --- a/engine/runtime/Cargo.toml +++ b/engine/runtime/Cargo.toml @@ -20,5 +20,8 @@ libc = { version = "0.2" } [target.'cfg(windows)'.dependencies] windows = { version = "0.36.0", features = ["Win32_System_Memory", "Win32_System_SystemInformation", "Win32_System_Diagnostics_Debug", "Win32_Foundation", "Win32_Security"] } +[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] +nix = { version = "0.24.1" } + [dev-dependencies] serde_json = { version = "1.0" } \ No newline at end of file diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 6df44bab2..3d7202738 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -1,8 +1,8 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! This module implements a memory allocator, that fragments two or more parts -//! of requested memory. There are some strategies to fragment multiple parts of memory. +//! This module provides functionality to allocate memory with higher randomness on returned addresses. +//! //! The most simple approach is to allocate memory multiple times, return the final allocation //! as the desired memory. //! @@ -13,7 +13,7 @@ //! //! [`FragStrategy`] implements at least two possible allocation strategies: //! -//! - Default: The algorithm tries to allocate a huge amount of memory space while keeping a certain address distance +//! - Direct: The algorithm tries to allocate a huge amount of memory space while keeping a certain address distance //! - Memory Mapped: anonymous memory is being mapping, the memory address will be randomly selected. use crate::MemoryError; @@ -99,133 +99,6 @@ impl Frag { // ----------------------------------------------------------------------------- -#[derive(Default, Clone)] -struct ForkAlloc; - -impl Alloc for ForkAlloc -where - T: Default, -{ - type Error = MemoryError; - - #[cfg(target_os = "windows")] - fn alloc() -> Result, Self::Error> { - todo!() - } - - #[cfg(any(target_os = "linux", target_os = "macos"))] - fn alloc() -> Result, Self::Error> { - use std::mem::MaybeUninit; - - let mut pipe = [-1_i32; 2]; - let piped_result = unsafe { libc::pipe(&mut pipe as *mut i32) }; - - if piped_result < 0 { - return Err(MemoryError::Allocation("Failed to create pipe".to_string())); - } - - // child process - let pid; - unsafe { - pid = libc::fork(); - - match pid { - 0 => { - std::panic::set_hook(Box::new(|_| { - libc::exit(0); - })); - - // ptrace hook - // libc::ptrace(libc::PTRACE_TRACEME); - - // todo: register error hooks - // allocate memory and free it immediately - // (0..10).for_each(|_| { - // MaybeUninit::<[u8; usize::MAX >> 45]>::uninit().as_mut_ptr(); - // }); - - let mut ptr = libc::malloc(usize::MAX); - let mut i: usize = 0; - while ptr.is_null() { - ptr = libc::malloc(usize::MAX >> i); - i = i.saturating_add(1); - } - - println!("a got allocated at {:p}", ptr); - - let mut ptr = libc::malloc(usize::MAX); - let mut i: usize = 0; - while ptr.is_null() { - ptr = libc::malloc(usize::MAX >> i); - i = i.saturating_add(1); - } - println!("b got allocated at {:p}", ptr); - libc::free(ptr); - - libc::dup2(pipe[1], 1); // map write to stdout - libc::close(pipe[0]); - - let mut object = T::default(); - let ptr = &mut object as *mut T as *mut u8; - let mut size = std::mem::size_of::(); - - while size > 0 { - let w = libc::write(1, ptr as *const libc::c_void, size); - if w < 0 { - return Err(MemoryError::Allocation("Failed to send allocated memory".to_string())); - } - - size -= w as usize; - } - - libc::_exit(0); - } - _ if pid < 0 => { - return Err(MemoryError::Allocation("Failed to fork process".to_string())); - } - - _ => { - println!("Got child pid: {}", pid); - } - }; - } - - unsafe { - let mut status = 0; - libc::waitpid(pid, &mut status, 0); - - match status { - _ if libc::WIFEXITED(status) => { - if libc::WIFSTOPPED(status) { - return Err(MemoryError::Allocation("Child process crash".to_string())); - } - - // read from pipe - let mut m: MaybeUninit = MaybeUninit::uninit(); - let mut size = std::mem::size_of::(); - let p = m.as_mut_ptr(); - println!("Reading from pipe"); - while size > 0 { - let read = libc::read(pipe[0], p as *mut libc::c_void, size); - if read < 0 { - return Err(MemoryError::Allocation("Failed to read from pipe".to_string())); - } - - size -= read as usize; - } - - Ok(NonNull::new_unchecked(p)) - } - _ => Err(MemoryError::Allocation( - "Unknown state while waiting for child process".to_string(), - )), - } - } - } -} - -// ----------------------------------------------------------------------------- - #[derive(Default, Clone)] struct MemoryMapAlloc; @@ -246,9 +119,12 @@ where loop { let mut addr: usize = rng.gen::(); + // the maximum size of the mapping + let max_alloc_size = 0xFFFFFF; + let ptr = libc::mmap( &mut addr as *mut usize as *mut libc::c_void, - size, + rng.gen::().min(size).max(max_alloc_size), libc::PROT_READ | libc::PROT_WRITE, libc::MAP_ANONYMOUS | libc::MAP_PRIVATE, -1, diff --git a/engine/runtime/tests/alloc_tests.rs b/engine/runtime/tests/alloc_tests.rs index 8d5fcd03e..a1207c5fb 100644 --- a/engine/runtime/tests/alloc_tests.rs +++ b/engine/runtime/tests/alloc_tests.rs @@ -1,7 +1,7 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::ptr::NonNull; +use std::{fmt::Debug, ptr::NonNull}; use runtime::{ memories::frag::{Frag, FragStrategy}, @@ -38,16 +38,22 @@ fn test_allocate_map() { fn test_allocate(allocator: F) -> Result<(), MemoryError> where - T: Default, + T: Default + Debug + PartialEq, F: Fn() -> Option<(NonNull, NonNull)>, { let min_distance = 0xFFFF; let result = allocator(); assert!(result.is_some()); + let (a, b) = result.unwrap(); unsafe { - assert!(distance(a.as_ref(), b.as_ref()) > min_distance); + let aa = a.as_ref(); + let bb = b.as_ref(); + + assert!(distance(aa, bb) > min_distance); + assert_eq!(aa, &T::default()); + assert_eq!(bb, &T::default()); } Ok(()) From 8d3a6298439ecf603c25d1e4f337bbca09175734 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Thu, 5 May 2022 16:44:32 +0200 Subject: [PATCH 07/46] fix: map large chunk of anonymous memory; randomize parts of it. --- engine/runtime/src/memories/frag.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 3d7202738..e0e0538f0 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -117,14 +117,16 @@ where unsafe { loop { - let mut addr: usize = rng.gen::(); + let mut addr: usize = (0x00007fff00000000 | (rng.gen::() >> 32)) & !0xFFF; // the maximum size of the mapping - let max_alloc_size = 0xFFFFFF; + let max_alloc_size = 0xFFFF; + let desired_alloc_size = rng.gen::().min(size).max(max_alloc_size); + // this creates an anonymous mapping zeroed out. let ptr = libc::mmap( - &mut addr as *mut usize as *mut libc::c_void, - rng.gen::().min(size).max(max_alloc_size), + &mut addr as *mut _ as *mut libc::c_void, + desired_alloc_size, // was size libc::PROT_READ | libc::PROT_WRITE, libc::MAP_ANONYMOUS | libc::MAP_PRIVATE, -1, @@ -135,6 +137,11 @@ where continue; } + // write random bytes into allocated pages + let bytes = ptr as *mut usize; + let end = rng.gen_range(size..(size * 0x1000)); + (size..end).for_each(|_| bytes.write(rng.gen())); + // on linux this isn't required to commit memory #[cfg(any(target_os = "macos"))] libc::madvise(&mut addr as *mut usize as *mut libc::c_void, size, libc::MADV_WILLNEED); @@ -309,6 +316,7 @@ where /// let c = runtime::memories::frag::round_up(n, b); /// assert_eq!(c, 14); /// ``` +#[inline(always)] pub fn round_up(value: usize, base: usize) -> usize { if base == 0 { return value; From d979a91c344008db0faf5710d89d35fd2b60a60e Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Fri, 6 May 2022 10:46:34 +0200 Subject: [PATCH 08/46] feat: try to retrieve page size from system --- engine/runtime/Cargo.toml | 4 ++- engine/runtime/src/memories/frag.rs | 53 ++++++++++++++++++++++++++--- engine/runtime/tests/alloc_tests.rs | 17 +++++++-- 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/engine/runtime/Cargo.toml b/engine/runtime/Cargo.toml index 3004b6099..af8856e27 100644 --- a/engine/runtime/Cargo.toml +++ b/engine/runtime/Cargo.toml @@ -16,6 +16,7 @@ dirs = { version = "4.0.0" } thiserror = { version = "1.0" } iota-crypto = { version = "0.8.0", features = ["sha"] } libc = { version = "0.2" } +log = { version = "0.4.17" } [target.'cfg(windows)'.dependencies] windows = { version = "0.36.0", features = ["Win32_System_Memory", "Win32_System_SystemInformation", "Win32_System_Diagnostics_Debug", "Win32_Foundation", "Win32_Security"] } @@ -24,4 +25,5 @@ windows = { version = "0.36.0", features = ["Win32_System_Memory nix = { version = "0.24.1" } [dev-dependencies] -serde_json = { version = "1.0" } \ No newline at end of file +serde_json = { version = "1.0" } +env_logger = { version = "0.9" } \ No newline at end of file diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index e0e0538f0..e375d56ce 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -17,6 +17,7 @@ //! - Memory Mapped: anonymous memory is being mapping, the memory address will be randomly selected. use crate::MemoryError; +use log::*; use std::{fmt::Debug, ptr::NonNull}; /// Fragmenting strategy to allocate memory at random addresses. @@ -99,6 +100,18 @@ impl Frag { // ----------------------------------------------------------------------------- +/// [`MemoryMapAlloc`] maps an anonymous file with an arbitrary large size. Parts +/// of this memory will be randomly seeded. +/// +/// The actual implementation is system dependent and might vary. +/// +/// # Example +/// ``` +/// use runtime::memories::frag::{Frag, FragStrategy}; +/// +/// // allocates the object at a random address +/// let object = Frag::alloc::(FragStrategy::Map).unwrap(); +/// ``` #[derive(Default, Clone)] struct MemoryMapAlloc; @@ -117,12 +130,22 @@ where unsafe { loop { - let mut addr: usize = (0x00007fff00000000 | (rng.gen::() >> 32)) & !0xFFF; + let pagesize = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE) + .unwrap_or(Some(0x1000i64)) + .unwrap() as usize; + + info!("Using page size {}", pagesize); + + let mut addr: usize = (0x00007fff00000000 | (rng.gen::() >> 32)) & (!0usize ^ (pagesize - 1)); + + info!("Using addr 0x{:08X}", addr); // the maximum size of the mapping let max_alloc_size = 0xFFFF; let desired_alloc_size = rng.gen::().min(size).max(max_alloc_size); + info!("prealloc: desired alloc size 0x{:08X}", desired_alloc_size); + // this creates an anonymous mapping zeroed out. let ptr = libc::mmap( &mut addr as *mut _ as *mut libc::c_void, @@ -133,6 +156,8 @@ where 0, ); + info!("preallocated segment {:p}", ptr); + if ptr == libc::MAP_FAILED { continue; } @@ -142,9 +167,17 @@ where let end = rng.gen_range(size..(size * 0x1000)); (size..end).for_each(|_| bytes.write(rng.gen())); - // on linux this isn't required to commit memory #[cfg(any(target_os = "macos"))] - libc::madvise(&mut addr as *mut usize as *mut libc::c_void, size, libc::MADV_WILLNEED); + { + // on linux this isn't required to commit memory + let error = libc::madvise(&mut addr as *mut usize as *mut libc::c_void, size, libc::MADV_WILLNEED); + + { + if error != 0 { + info!("madvise returned an error {}", err); + } + } + } let ptr = ptr as *mut T; @@ -152,6 +185,8 @@ where let t = T::default(); ptr.write(t); + info!("Object sucesfully written into mem location"); + return Ok(NonNull::new_unchecked(ptr)); } } @@ -265,11 +300,21 @@ where loop { unsafe { let alloc_size = rng.gen::() >> 32; + let actual_size = std::mem::size_of::(); let mem_ptr = libc::malloc(alloc_size); + if mem_ptr.is_null() { continue; } - let actual_size = std::mem::size_of::(); + + // on linux this isn't required to commit memory + #[cfg(any(target_os = "macos"))] + libc::madvise( + &mut mem_ptr as *mut usize as *mut libc::c_void, + size, + libc::MADV_WILLNEED, + ); + let actual_mem = libc::realloc(mem_ptr, actual_size) as *mut T; actual_mem.write(T::default()); diff --git a/engine/runtime/tests/alloc_tests.rs b/engine/runtime/tests/alloc_tests.rs index a1207c5fb..df778d463 100644 --- a/engine/runtime/tests/alloc_tests.rs +++ b/engine/runtime/tests/alloc_tests.rs @@ -1,12 +1,12 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::{fmt::Debug, ptr::NonNull}; - +use log::*; use runtime::{ memories::frag::{Frag, FragStrategy}, MemoryError, }; +use std::{fmt::Debug, ptr::NonNull}; #[derive(PartialEq, Debug, Clone)] struct TestStruct { @@ -26,13 +26,26 @@ impl Default for TestStruct { /// this fails under windows #[test] fn test_allocate_direct() { + let _ = env_logger::builder() + .is_test(true) + .filter(None, log::LevelFilter::Info) + .try_init(); + assert!(test_allocate::(|| Frag::alloc(FragStrategy::Direct)).is_ok()); assert!(test_allocate::(|| Frag::alloc2(FragStrategy::Direct, 0xFFFF)).is_ok()); } #[test] fn test_allocate_map() { + let _ = env_logger::builder() + .is_test(true) + .filter(None, log::LevelFilter::Info) + .try_init(); + + info!("Test Fixed Distance"); assert!(test_allocate::(|| Frag::alloc(FragStrategy::Map)).is_ok()); + + info!("Test Arbitrary Distance"); assert!(test_allocate::(|| Frag::alloc2(FragStrategy::Map, 0xFFFF)).is_ok()); } From 8a7221d96f48d24dc658d6a1b826085ce0022ca0 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Fri, 6 May 2022 10:57:25 +0200 Subject: [PATCH 09/46] fixes: missing values --- engine/runtime/src/memories/frag.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index e375d56ce..ecb31236b 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -174,7 +174,7 @@ where { if error != 0 { - info!("madvise returned an error {}", err); + info!("madvise returned an error {}", error); } } } @@ -309,11 +309,7 @@ where // on linux this isn't required to commit memory #[cfg(any(target_os = "macos"))] - libc::madvise( - &mut mem_ptr as *mut usize as *mut libc::c_void, - size, - libc::MADV_WILLNEED, - ); + libc::madvise(mem_ptr, actual_size, libc::MADV_WILLNEED); let actual_mem = libc::realloc(mem_ptr, actual_size) as *mut T; actual_mem.write(T::default()); From e24b33af765dd75b5505630ecd5f42d8fe6cba2b Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Fri, 6 May 2022 11:14:18 +0200 Subject: [PATCH 10/46] inspect: increase log --- engine/runtime/src/memories/frag.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index ecb31236b..eebc86907 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -301,19 +301,34 @@ where unsafe { let alloc_size = rng.gen::() >> 32; let actual_size = std::mem::size_of::(); + + info!("desired alloc size 0x{:08X}", actual_size); + let mem_ptr = libc::malloc(alloc_size); + info!("allocated block {:p}", mem_ptr); + if mem_ptr.is_null() { continue; } // on linux this isn't required to commit memory #[cfg(any(target_os = "macos"))] - libc::madvise(mem_ptr, actual_size, libc::MADV_WILLNEED); + { + let error = libc::madvise(mem_ptr, actual_size, libc::MADV_WILLNEED); + + if error != 0 { + info!("memory advise returned an error {}", error); + } + } let actual_mem = libc::realloc(mem_ptr, actual_size) as *mut T; + info!("memory was reallocated {:p}", actual_mem); + actual_mem.write(T::default()); + info!("writing object was successful "); + return Ok(NonNull::new_unchecked(actual_mem)); } } From 413ef209d832c2cc2b79398e8a32be31f3647772 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Fri, 6 May 2022 13:10:16 +0200 Subject: [PATCH 11/46] fix: change return types / more verbose output --- engine/benches/benchmark.rs | 4 ++-- engine/runtime/src/memories/frag.rs | 31 +++++++++++++++++------------ engine/runtime/tests/alloc_tests.rs | 7 +++++-- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/engine/benches/benchmark.rs b/engine/benches/benchmark.rs index 6a5144164..c48ee8333 100644 --- a/engine/benches/benchmark.rs +++ b/engine/benches/benchmark.rs @@ -126,7 +126,7 @@ fn bench_allocate_direct(c: &mut Criterion) { c.bench_function("Allocate memory direct", |b| { b.iter(|| { - Frag::alloc::(FragStrategy::Direct); + let _ = Frag::alloc::(FragStrategy::Direct); }); }); } @@ -149,7 +149,7 @@ fn bench_allocate_mapped(c: &mut Criterion) { c.bench_function("Allocate memory mapped", |b| { b.iter(|| { - Frag::alloc::(FragStrategy::Map); + let _ = Frag::alloc::(FragStrategy::Map); }); }); } diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index eebc86907..ab1b34955 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -68,7 +68,7 @@ impl Frag { } /// Tries to allocate two objects of the same type with a minimum distance in memory space. - pub fn alloc2(strategy: FragStrategy, distance: usize) -> Option<(NonNull, NonNull)> + pub fn alloc2(strategy: FragStrategy, distance: usize) -> Result<(NonNull, NonNull), MemoryError> where T: Default, { @@ -79,18 +79,20 @@ impl Frag { a.abs_diff(b) }; - let a = Self::alloc_single::(strategy).ok()?; - let b = Self::alloc_single::(strategy).ok()?; + let a = Self::alloc_single::(strategy)?; + let b = Self::alloc_single::(strategy)?; unsafe { if d(a.as_ref(), b.as_ref()) < distance { - return None; + return Err(MemoryError::Allocation( + "Distance between segments below threshold".to_owned(), + )); } } - Some((a, b)) + Ok((a, b)) } /// Tries to allocate two objects of the same type with a default minimum distance in memory space of `0xFFFF`. - pub fn alloc(strategy: FragStrategy) -> Option<(NonNull, NonNull)> + pub fn alloc(strategy: FragStrategy) -> Result<(NonNull, NonNull), MemoryError> where T: Default, { @@ -174,7 +176,7 @@ where { if error != 0 { - info!("madvise returned an error {}", error); + error!("madvise returned an error {}", error); } } } @@ -296,11 +298,12 @@ where fn alloc() -> Result, Self::Error> { use random::{thread_rng, Rng}; + let actual_size = std::mem::size_of::(); let mut rng = thread_rng(); + loop { unsafe { - let alloc_size = rng.gen::() >> 32; - let actual_size = std::mem::size_of::(); + let alloc_size = rng.gen::().min(actual_size).max(0xFFFFF); info!("desired alloc size 0x{:08X}", actual_size); @@ -318,7 +321,7 @@ where let error = libc::madvise(mem_ptr, actual_size, libc::MADV_WILLNEED); if error != 0 { - info!("memory advise returned an error {}", error); + error!("memory advise returned an error {}", error); } } @@ -336,15 +339,17 @@ where #[cfg(target_os = "windows")] fn alloc() -> Result, Self::Error> { + use windows::Win32::System::Memory::{VirtualAlloc, MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE}; + loop { unsafe { let actual_size = std::mem::size_of::(); - let actual_mem = windows::Win32::System::Memory::VirtualAlloc( + let actual_mem = VirtualAlloc( std::ptr::null_mut(), actual_size, - windows::Win32::System::Memory::MEM_COMMIT | windows::Win32::System::Memory::MEM_RESERVE, - windows::Win32::System::Memory::PAGE_READWRITE, + MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE, ); if actual_mem.is_null() { diff --git a/engine/runtime/tests/alloc_tests.rs b/engine/runtime/tests/alloc_tests.rs index df778d463..e97059b95 100644 --- a/engine/runtime/tests/alloc_tests.rs +++ b/engine/runtime/tests/alloc_tests.rs @@ -31,7 +31,10 @@ fn test_allocate_direct() { .filter(None, log::LevelFilter::Info) .try_init(); + info!("Test Fixed Distance"); assert!(test_allocate::(|| Frag::alloc(FragStrategy::Direct)).is_ok()); + + info!("Test Arbitrary Distance"); assert!(test_allocate::(|| Frag::alloc2(FragStrategy::Direct, 0xFFFF)).is_ok()); } @@ -52,11 +55,11 @@ fn test_allocate_map() { fn test_allocate(allocator: F) -> Result<(), MemoryError> where T: Default + Debug + PartialEq, - F: Fn() -> Option<(NonNull, NonNull)>, + F: Fn() -> Result<(NonNull, NonNull), MemoryError>, { let min_distance = 0xFFFF; let result = allocator(); - assert!(result.is_some()); + assert!(result.is_ok(), "Failed to allocate memory: {:?}", result); let (a, b) = result.unwrap(); From 81eaa040d01c32fb5292e6f39b72447c9bf75ab2 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Mon, 9 May 2022 08:46:27 +0200 Subject: [PATCH 12/46] fix: randomize adress for direct allocation on *nix --- engine/runtime/src/memories/frag.rs | 94 +++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 25 deletions(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index ab1b34955..48434f754 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -84,7 +84,7 @@ impl Frag { unsafe { if d(a.as_ref(), b.as_ref()) < distance { return Err(MemoryError::Allocation( - "Distance between segments below threshold".to_owned(), + "Distance between parts below threshold".to_owned(), )); } } @@ -130,14 +130,13 @@ where use random::{thread_rng, Rng}; let mut rng = thread_rng(); + let pagesize = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE) + .unwrap_or(Some(0x1000i64)) + .unwrap() as usize; + info!("Using page size {}", pagesize); + unsafe { loop { - let pagesize = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE) - .unwrap_or(Some(0x1000i64)) - .unwrap() as usize; - - info!("Using page size {}", pagesize); - let mut addr: usize = (0x00007fff00000000 | (rng.gen::() >> 32)) & (!0usize ^ (pagesize - 1)); info!("Using addr 0x{:08X}", addr); @@ -301,36 +300,81 @@ where let actual_size = std::mem::size_of::(); let mut rng = thread_rng(); + let min = 0xFFFF; + let max = 0xFFFF_FFFF; + + #[cfg(any(target_os = "unix", target_os = "linux"))] + let _pagesize = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE) + .unwrap_or(Some(0x1000i64)) + .unwrap() as usize; + + // Within the loop we allocate a sufficiently "large" chunk of memory. A random + // offset will be added to the returned pointer and the object will be written. This + // actually leaks memory. loop { unsafe { - let alloc_size = rng.gen::().min(actual_size).max(0xFFFFF); - - info!("desired alloc size 0x{:08X}", actual_size); + let mem_ptr = { + let alloc_size = rng.gen::().min(min).max(max); // min was actual_size - let mem_ptr = libc::malloc(alloc_size); + info!("desired alloc size 0x{:08X}", alloc_size); - info!("allocated block {:p}", mem_ptr); + // allocate some randomly sized chunk of memory + let ptr = libc::malloc(alloc_size); - if mem_ptr.is_null() { - continue; - } + info!("allocated block {:p}", ptr); - // on linux this isn't required to commit memory - #[cfg(any(target_os = "macos"))] - { - let error = libc::madvise(mem_ptr, actual_size, libc::MADV_WILLNEED); + if ptr.is_null() { + continue; + } - if error != 0 { - error!("memory advise returned an error {}", error); + #[cfg(target_os = "macos")] + { + // on linux it isn't required to commit memory + let error = libc::madvise(mem_ptr, actual_size, libc::MADV_WILLNEED); + if error != 0 { + error!("memory advise returned an error {}", error); + continue; + } } - } - let actual_mem = libc::realloc(mem_ptr, actual_size) as *mut T; - info!("memory was reallocated {:p}", actual_mem); + ptr + }; + + // let mem_ptr = { + // let alloc_size = rng.gen::().min(actual_size).max(0xFFFFF); + + // // info!("desired alloc size 0x{:08X}", actual_size); + // // allocate some randomly sized blob + // let mem_ptr = libc::malloc(alloc_size); + // info!("allocated block {:p}", mem_ptr); + // if mem_ptr.is_null() { + // continue; + // } + // #[cfg(macos)] + // { + // // on linux it isn't required to commit memory + // let error = libc::madvise(mem_ptr, actual_size, libc::MADV_WILLNEED); + // if error != 0 { + // error!("memory advise returned an error {}", error); + // } + // } + + // mem_ptr + // }; + + // we are searching for some address in between + let offset = rng.gen::().min(max - actual_size); + info!("Generated memory offset for pointer: 0x{:08X}", offset); + + let actual_mem = ((mem_ptr as usize) + offset) as *mut T; + + // let actual_mem = libc::realloc(mem_ptr as *mut libc::c_void, actual_size) as *mut T; + + info!("memory is now located at {:p}", actual_mem); actual_mem.write(T::default()); - info!("writing object was successful "); + info!("writing object was successful"); return Ok(NonNull::new_unchecked(actual_mem)); } From fe4b23efd6ee27b5720202f06d49a0c4b320fc4b Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Mon, 9 May 2022 08:52:52 +0200 Subject: [PATCH 13/46] fix: wrong parameter name --- engine/runtime/src/memories/frag.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 48434f754..2fd116fa5 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -330,7 +330,7 @@ where #[cfg(target_os = "macos")] { // on linux it isn't required to commit memory - let error = libc::madvise(mem_ptr, actual_size, libc::MADV_WILLNEED); + let error = libc::madvise(ptr, actual_size, libc::MADV_WILLNEED); if error != 0 { error!("memory advise returned an error {}", error); continue; From 6d7feb487d0efd9839c4eada1ebccf8c9fab4faf Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Mon, 9 May 2022 09:22:14 +0200 Subject: [PATCH 14/46] fix: code cleanup. removed commented out code --- engine/runtime/src/memories/frag.rs | 38 +---------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 2fd116fa5..34a44ca7a 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -314,15 +314,10 @@ where loop { unsafe { let mem_ptr = { - let alloc_size = rng.gen::().min(min).max(max); // min was actual_size - - info!("desired alloc size 0x{:08X}", alloc_size); + let alloc_size = rng.gen::().min(min).max(max); // allocate some randomly sized chunk of memory let ptr = libc::malloc(alloc_size); - - info!("allocated block {:p}", ptr); - if ptr.is_null() { continue; } @@ -340,42 +335,11 @@ where ptr }; - // let mem_ptr = { - // let alloc_size = rng.gen::().min(actual_size).max(0xFFFFF); - - // // info!("desired alloc size 0x{:08X}", actual_size); - // // allocate some randomly sized blob - // let mem_ptr = libc::malloc(alloc_size); - // info!("allocated block {:p}", mem_ptr); - // if mem_ptr.is_null() { - // continue; - // } - // #[cfg(macos)] - // { - // // on linux it isn't required to commit memory - // let error = libc::madvise(mem_ptr, actual_size, libc::MADV_WILLNEED); - // if error != 0 { - // error!("memory advise returned an error {}", error); - // } - // } - - // mem_ptr - // }; - // we are searching for some address in between let offset = rng.gen::().min(max - actual_size); - info!("Generated memory offset for pointer: 0x{:08X}", offset); - let actual_mem = ((mem_ptr as usize) + offset) as *mut T; - - // let actual_mem = libc::realloc(mem_ptr as *mut libc::c_void, actual_size) as *mut T; - - info!("memory is now located at {:p}", actual_mem); - actual_mem.write(T::default()); - info!("writing object was successful"); - return Ok(NonNull::new_unchecked(actual_mem)); } } From f9978ccb2d71c191452fd61cd4aa7535f3ccd69f Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Wed, 11 May 2022 10:55:17 +0200 Subject: [PATCH 15/46] fix: remove random initialization; introduced config --- engine/runtime/src/memories/frag.rs | 138 ++++++++++++++++++++-------- engine/runtime/tests/alloc_tests.rs | 3 +- 2 files changed, 102 insertions(+), 39 deletions(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 34a44ca7a..f349ba0ab 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -27,7 +27,7 @@ pub enum FragStrategy { /// Anonymously maps a region of memory Map, - /// System's allocator will be called a few times + /// Using system allocator (`malloc` on linux/bsd/macos and `VirtualAlloc` on windows) Direct, } @@ -37,8 +37,9 @@ pub enum FragStrategy { pub trait Alloc { type Error; - /// Allocates `T`, returns an error if something wrong happened - fn alloc() -> Result, Self::Error>; + /// Allocates `T`, returns an error if something wrong happened. Takes an + /// optional configuration to check against a previous allocation + fn alloc(config: Option) -> Result, Self::Error>; } // ----------------------------------------------------------------------------- @@ -47,6 +48,27 @@ pub trait Alloc { /// according to their strategy pub struct Frag; +/// Configuration for the fragmenting allocator +pub struct FragConfig { + /// The last address of a previous allocation. This + /// value will be used to calculate the minimum distance to the + /// previous allocation. + pub(crate) last_address: usize, + + /// The minimum distance to a previous allocation + pub(crate) min_distance: usize, +} + +impl FragConfig { + /// Creates a new [`FragConfig`] + pub fn new(last_address: usize, min_distance: usize) -> Self { + Self { + last_address, + min_distance, + } + } +} + impl Frag { /// Returns a fragmenting allocator by strategy /// @@ -57,13 +79,13 @@ impl Frag { /// /// let object = Frag::by_strategy(FragStrategy::Default).unwrap(); /// ``` - pub fn alloc_single(s: FragStrategy) -> Result, MemoryError> + pub fn alloc_single(strategy: FragStrategy, config: Option) -> Result, MemoryError> where T: Default, { - match s { - FragStrategy::Direct => DirectAlloc::alloc(), - FragStrategy::Map => MemoryMapAlloc::alloc(), + match strategy { + FragStrategy::Direct => DirectAlloc::alloc(config), + FragStrategy::Map => MemoryMapAlloc::alloc(config), } } @@ -72,20 +94,15 @@ impl Frag { where T: Default, { - let d = |a: &T, b: &T| { - let a = a as *const T as usize; - let b = b as *const T as usize; - - a.abs_diff(b) - }; - - let a = Self::alloc_single::(strategy)?; - let b = Self::alloc_single::(strategy)?; + let a = Self::alloc_single::(strategy, None)?; + let b = Self::alloc_single::(strategy, Some(FragConfig::new(&a as *const _ as usize, distance)))?; unsafe { - if d(a.as_ref(), b.as_ref()) < distance { - return Err(MemoryError::Allocation( - "Distance between parts below threshold".to_owned(), - )); + let actual_distance = calc_distance(a.as_ref(), b.as_ref()); + if actual_distance < distance { + return Err(MemoryError::Allocation(format!( + "Distance between parts below threshold: 0x{:016X}", + actual_distance + ))); } } @@ -124,49 +141,60 @@ where type Error = MemoryError; #[cfg(any(target_os = "linux", target_os = "macos"))] - fn alloc() -> Result, Self::Error> { + fn alloc(config: Option) -> Result, Self::Error> { + let hr = "-".repeat(20); + info!("{0}Mapping Allocator{0}", hr); + let size = std::mem::size_of::(); use random::{thread_rng, Rng}; let mut rng = thread_rng(); + let default_page_size = 0x1000i64; + let pagesize = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE) - .unwrap_or(Some(0x1000i64)) + .unwrap_or(Some(default_page_size)) .unwrap() as usize; + info!("Using page size {}", pagesize); unsafe { loop { - let mut addr: usize = (0x00007fff00000000 | (rng.gen::() >> 32)) & (!0usize ^ (pagesize - 1)); + let mut addr: usize = (rng.gen::() >> 32) & (!0usize ^ (pagesize - 1)); - info!("Using addr 0x{:08X}", addr); + info!("Desired addr 0x{:08X}", addr); // the maximum size of the mapping - let max_alloc_size = 0xFFFF; - let desired_alloc_size = rng.gen::().min(size).max(max_alloc_size); + let max_alloc_size = 0xFFFFFF; + + let desired_alloc_size: usize = rng.gen_range(size..=max_alloc_size); info!("prealloc: desired alloc size 0x{:08X}", desired_alloc_size); // this creates an anonymous mapping zeroed out. let ptr = libc::mmap( &mut addr as *mut _ as *mut libc::c_void, - desired_alloc_size, // was size + desired_alloc_size, libc::PROT_READ | libc::PROT_WRITE, libc::MAP_ANONYMOUS | libc::MAP_PRIVATE, -1, 0, ); - info!("preallocated segment {:p}", ptr); + info!("Preallocated segment addr: {:p}", ptr); if ptr == libc::MAP_FAILED { + warn!("Memory mapping failed"); continue; } - // write random bytes into allocated pages - let bytes = ptr as *mut usize; - let end = rng.gen_range(size..(size * 0x1000)); - (size..end).for_each(|_| bytes.write(rng.gen())); + if let Some(ref cfg) = config { + let actual_distance = (ptr as usize).abs_diff(cfg.last_address); + if actual_distance < cfg.min_distance { + warn!("New allocation distance to previous allocation is below threshold."); + continue; + } + } #[cfg(any(target_os = "macos"))] { @@ -176,6 +204,7 @@ where { if error != 0 { error!("madvise returned an error {}", error); + continue; } } } @@ -186,7 +215,7 @@ where let t = T::default(); ptr.write(t); - info!("Object sucesfully written into mem location"); + info!("Object succesfully written into mem location"); return Ok(NonNull::new_unchecked(ptr)); } @@ -195,7 +224,7 @@ where } #[cfg(target_os = "windows")] - fn alloc() -> Result, Self::Error> { + fn alloc(config: Option) -> Result, Self::Error> { use random::{thread_rng, Rng}; let mut rng = thread_rng(); @@ -220,6 +249,14 @@ where return Err(e); } + if let Some(ref cfg) = config { + let actual_distance = (ptr as usize).abs_diff(cfg.last_address); + if actual_distance < cfg.min_distance { + warn!("New allocation distance to previous allocation is below threshold."); + continue; + } + } + let _ = windows::Win32::System::Memory::MapViewOfFile( random_mapping, windows::Win32::System::Memory::FILE_MAP_ALL_ACCESS, @@ -294,7 +331,7 @@ where type Error = MemoryError; #[cfg(any(target_os = "linux", target_os = "macos"))] - fn alloc() -> Result, Self::Error> { + fn alloc(config: Option) -> Result, Self::Error> { use random::{thread_rng, Rng}; let actual_size = std::mem::size_of::(); @@ -303,9 +340,12 @@ where let min = 0xFFFF; let max = 0xFFFF_FFFF; + // pick a default, if system api call is not successful + let default_page_size = 0x1000i64; + #[cfg(any(target_os = "unix", target_os = "linux"))] let _pagesize = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE) - .unwrap_or(Some(0x1000i64)) + .unwrap_or(Some(default_page_size)) .unwrap() as usize; // Within the loop we allocate a sufficiently "large" chunk of memory. A random @@ -335,6 +375,14 @@ where ptr }; + if let Some(ref cfg) = config { + let actual_distance = (mem_ptr as usize).abs_diff(cfg.last_address); + if actual_distance < cfg.min_distance { + warn!("New allocation distance to previous allocation is below threshold."); + continue; + } + } + // we are searching for some address in between let offset = rng.gen::().min(max - actual_size); let actual_mem = ((mem_ptr as usize) + offset) as *mut T; @@ -346,7 +394,7 @@ where } #[cfg(target_os = "windows")] - fn alloc() -> Result, Self::Error> { + fn alloc(config: Option) -> Result, Self::Error> { use windows::Win32::System::Memory::{VirtualAlloc, MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE}; loop { @@ -366,6 +414,14 @@ where } } + if let Some(ref cfg) = config { + let actual_distance = (actual_mem as usize).abs_diff(cfg.last_address); + if actual_distance < cfg.min_distance { + warn!("New allocation distance to previous allocation is below threshold."); + continue; + } + } + let actual_mem = actual_mem as *mut T; actual_mem.write(T::default()); return Ok(NonNull::new_unchecked(actual_mem)); @@ -397,6 +453,14 @@ pub fn round_up(value: usize, base: usize) -> usize { } } +/// Calulates the distance between two pointers and returns it +fn calc_distance(a: &T, b: &T) -> usize { + let a = a as *const T as usize; + let b = b as *const T as usize; + + a.abs_diff(b) +} + /// Checks for error codes under Windows. /// /// Detected errors will be returned as [`MemoryError`]. If no error has been diff --git a/engine/runtime/tests/alloc_tests.rs b/engine/runtime/tests/alloc_tests.rs index e97059b95..a814ab084 100644 --- a/engine/runtime/tests/alloc_tests.rs +++ b/engine/runtime/tests/alloc_tests.rs @@ -23,7 +23,6 @@ impl Default for TestStruct { } } -/// this fails under windows #[test] fn test_allocate_direct() { let _ = env_logger::builder() @@ -67,7 +66,7 @@ where let aa = a.as_ref(); let bb = b.as_ref(); - assert!(distance(aa, bb) > min_distance); + assert!(distance(aa, bb) >= min_distance); assert_eq!(aa, &T::default()); assert_eq!(bb, &T::default()); } From c53df0a776df13233f7841f02486e77892d7fa10 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Wed, 11 May 2022 11:19:01 +0200 Subject: [PATCH 16/46] fix: wrap windows direct memory allocation inside a loop --- engine/runtime/src/memories/frag.rs | 125 ++++++++++++++-------------- 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index f349ba0ab..0bc44ae4d 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -229,79 +229,80 @@ where let mut rng = thread_rng(); let handle = windows::Win32::Foundation::INVALID_HANDLE_VALUE; + loop { + unsafe { + // allocation prelude + { + let r_addr = rng.gen::() >> 4; + + let random_mapping = windows::Win32::System::Memory::CreateFileMappingW( + handle, + std::ptr::null_mut(), + windows::Win32::System::Memory::PAGE_READWRITE, + 0, + r_addr, + windows::core::PCWSTR(std::ptr::null_mut()), + ) + .map_err(|e| MemoryError::Allocation(e.to_string()))?; + + if let Err(e) = last_error() { + return Err(e); + } - unsafe { - // allocation prelude - { - let r_addr = rng.gen::() >> 4; - - let random_mapping = windows::Win32::System::Memory::CreateFileMappingW( - handle, - std::ptr::null_mut(), - windows::Win32::System::Memory::PAGE_READWRITE, - 0, - r_addr, - windows::core::PCWSTR(std::ptr::null_mut()), - ) - .map_err(|e| MemoryError::Allocation(e.to_string()))?; + if let Some(ref cfg) = config { + let actual_distance = (ptr as usize).abs_diff(cfg.last_address); + if actual_distance < cfg.min_distance { + warn!("New allocation distance to previous allocation is below threshold."); + continue; + } + } - if let Err(e) = last_error() { - return Err(e); - } + let _ = windows::Win32::System::Memory::MapViewOfFile( + random_mapping, + windows::Win32::System::Memory::FILE_MAP_ALL_ACCESS, + 0, + 0, + r_addr as usize, + ); - if let Some(ref cfg) = config { - let actual_distance = (ptr as usize).abs_diff(cfg.last_address); - if actual_distance < cfg.min_distance { - warn!("New allocation distance to previous allocation is below threshold."); - continue; + if let Err(e) = last_error() { + return Err(e); } } - let _ = windows::Win32::System::Memory::MapViewOfFile( - random_mapping, - windows::Win32::System::Memory::FILE_MAP_ALL_ACCESS, - 0, - 0, - r_addr as usize, - ); + // actual memory mapping + { + let actual_size = std::mem::size_of::() as u32; + let actual_mapping = windows::Win32::System::Memory::CreateFileMappingW( + handle, + std::ptr::null_mut(), + windows::Win32::System::Memory::PAGE_READWRITE, + 0, + actual_size, + windows::core::PCWSTR(std::ptr::null_mut()), + ) + .map_err(|e| MemoryError::Allocation(e.to_string()))?; + + if let Err(e) = last_error() { + return Err(e); + } - if let Err(e) = last_error() { - return Err(e); - } - } + let actual_mem = windows::Win32::System::Memory::MapViewOfFile( + actual_mapping, + windows::Win32::System::Memory::FILE_MAP_ALL_ACCESS, + 0, + 0, + actual_size as usize, + ) as *mut T; - // actual memory mapping - { - let actual_size = std::mem::size_of::() as u32; - let actual_mapping = windows::Win32::System::Memory::CreateFileMappingW( - handle, - std::ptr::null_mut(), - windows::Win32::System::Memory::PAGE_READWRITE, - 0, - actual_size, - windows::core::PCWSTR(std::ptr::null_mut()), - ) - .map_err(|e| MemoryError::Allocation(e.to_string()))?; - - if let Err(e) = last_error() { - return Err(e); - } + if let Err(e) = last_error() { + return Err(e); + } - let actual_mem = windows::Win32::System::Memory::MapViewOfFile( - actual_mapping, - windows::Win32::System::Memory::FILE_MAP_ALL_ACCESS, - 0, - 0, - actual_size as usize, - ) as *mut T; + actual_mem.write(T::default()); - if let Err(e) = last_error() { - return Err(e); + return Ok(NonNull::new_unchecked(actual_mem as *mut T)); } - - actual_mem.write(T::default()); - - return Ok(NonNull::new_unchecked(actual_mem as *mut T)); } } } From 17b12ba25d25726fc6c6ebe662a6711ee1830678 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Wed, 11 May 2022 11:52:21 +0200 Subject: [PATCH 17/46] fix: wrong var reference --- engine/runtime/src/memories/frag.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 0bc44ae4d..9e56dbe70 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -250,7 +250,7 @@ where } if let Some(ref cfg) = config { - let actual_distance = (ptr as usize).abs_diff(cfg.last_address); + let actual_distance = (random_mapping as usize).abs_diff(cfg.last_address); if actual_distance < cfg.min_distance { warn!("New allocation distance to previous allocation is below threshold."); continue; From 96b8387f284f65935bc9b74ff0cd3cf7a5c06073 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Wed, 11 May 2022 12:06:28 +0200 Subject: [PATCH 18/46] fix: wrong type cast --- engine/runtime/src/memories/frag.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 9e56dbe70..699c7051a 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -250,7 +250,7 @@ where } if let Some(ref cfg) = config { - let actual_distance = (random_mapping as usize).abs_diff(cfg.last_address); + let actual_distance = (random_mapping as *const _ as usize).abs_diff(cfg.last_address); if actual_distance < cfg.min_distance { warn!("New allocation distance to previous allocation is below threshold."); continue; From 0ac3f7e0c0149ccf50b71e3618e8efa022044100 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Wed, 11 May 2022 12:16:45 +0200 Subject: [PATCH 19/46] fix(win): wrong type cast --- engine/runtime/src/memories/frag.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 699c7051a..6611cd13a 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -250,7 +250,7 @@ where } if let Some(ref cfg) = config { - let actual_distance = (random_mapping as *const _ as usize).abs_diff(cfg.last_address); + let actual_distance = (&random_mapping as *const _ as usize).abs_diff(cfg.last_address); if actual_distance < cfg.min_distance { warn!("New allocation distance to previous allocation is below threshold."); continue; From b839920bc6665b6cf669e3804d8b16d93dc2eae9 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Wed, 11 May 2022 12:31:41 +0200 Subject: [PATCH 20/46] fix(win): remove previous file mapping, if it has failed according to parameters --- engine/runtime/src/memories/frag.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 6611cd13a..e062d1527 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -250,9 +250,17 @@ where } if let Some(ref cfg) = config { - let actual_distance = (&random_mapping as *const _ as usize).abs_diff(cfg.last_address); + let actual_distance = (&*random_mapping as *const _ as usize).abs_diff(cfg.last_address); if actual_distance < cfg.min_distance { warn!("New allocation distance to previous allocation is below threshold."); + + // remove previous file mapping + windows::Win32::System::Memory::UnmapViewOfFile(random_mapping); + + if let Err(e) = last_error() { + return Err(e); + } + continue; } } From 40c83649b1862c1a753622feef3ddead6c8dcf38 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Wed, 11 May 2022 12:33:40 +0200 Subject: [PATCH 21/46] fix(win): check for error on unmap --- engine/runtime/src/memories/frag.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index e062d1527..3e7842ff3 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -255,10 +255,10 @@ where warn!("New allocation distance to previous allocation is below threshold."); // remove previous file mapping - windows::Win32::System::Memory::UnmapViewOfFile(random_mapping); - - if let Err(e) = last_error() { - return Err(e); + if !windows::Win32::System::Memory::UnmapViewOfFile(random_mapping) { + if let Err(e) = last_error() { + return Err(e); + } } continue; From 000223772228bf083598cc1e73283d2cf983f48d Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Wed, 11 May 2022 12:40:18 +0200 Subject: [PATCH 22/46] fix(win): BOOL type conversion --- engine/runtime/src/memories/frag.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 3e7842ff3..638eb87d0 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -255,7 +255,7 @@ where warn!("New allocation distance to previous allocation is below threshold."); // remove previous file mapping - if !windows::Win32::System::Memory::UnmapViewOfFile(random_mapping) { + if !windows::Win32::System::Memory::UnmapViewOfFile(random_mapping).as_bool() { if let Err(e) = last_error() { return Err(e); } From 4756edcb67c1043a5f2e010518451418062234b2 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Wed, 11 May 2022 13:50:02 +0200 Subject: [PATCH 23/46] fix: use ptr from mapping --- engine/runtime/src/memories/frag.rs | 33 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 638eb87d0..5b6f1d032 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -249,13 +249,28 @@ where return Err(e); } + let ptr = windows::Win32::System::Memory::MapViewOfFile( + random_mapping, + windows::Win32::System::Memory::FILE_MAP_ALL_ACCESS, + 0, + 0, + r_addr as usize, + ); + + if let Err(e) = last_error() { + return Err(e); + } + if let Some(ref cfg) = config { - let actual_distance = (&*random_mapping as *const _ as usize).abs_diff(cfg.last_address); + let actual_distance = (ptr as *const _ as usize).abs_diff(cfg.last_address); if actual_distance < cfg.min_distance { - warn!("New allocation distance to previous allocation is below threshold."); + warn!( + "New allocation distance to previous allocation is below threshold: {}", + actual_distance + ); // remove previous file mapping - if !windows::Win32::System::Memory::UnmapViewOfFile(random_mapping).as_bool() { + if !windows::Win32::System::Memory::UnmapViewOfFile(ptr).as_bool() { if let Err(e) = last_error() { return Err(e); } @@ -264,18 +279,6 @@ where continue; } } - - let _ = windows::Win32::System::Memory::MapViewOfFile( - random_mapping, - windows::Win32::System::Memory::FILE_MAP_ALL_ACCESS, - 0, - 0, - r_addr as usize, - ); - - if let Err(e) = last_error() { - return Err(e); - } } // actual memory mapping From 24c8917930fce7897c7198e5bf55bc979d411251 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Tue, 17 May 2022 13:09:57 +0200 Subject: [PATCH 24/46] regress: build back update mechanism --- engine/runtime/README.md | 5 ++ engine/runtime/src/boxed.rs | 42 ++++++++- engine/runtime/src/lib.rs | 3 + engine/runtime/src/memories/buffer.rs | 11 ++- engine/runtime/src/memories/frag.rs | 2 +- .../src/memories/noncontiguous_memory.rs | 87 ++++++++++++++++--- engine/runtime/src/memories/ram_memory.rs | 9 +- engine/runtime/src/utils.rs | 9 +- engine/runtime/tests/alloc_tests.rs | 1 + engine/runtime/tests/locked_memory_tests.rs | 2 + 10 files changed, 148 insertions(+), 23 deletions(-) diff --git a/engine/runtime/README.md b/engine/runtime/README.md index 1a71a1d3a..9b9e2a2ba 100644 --- a/engine/runtime/README.md +++ b/engine/runtime/README.md @@ -83,3 +83,8 @@ Hence data security depends on the strength of the encryption scheme and the 'ob - [ ] access to the locked memory - [ ] Benchmarks - [ ] no-std + + +- +0x00007ffff7fc4fe0 +0x00007ffff7fc0fe0 \ No newline at end of file diff --git a/engine/runtime/src/boxed.rs b/engine/runtime/src/boxed.rs index b48b99d10..b16ba3abf 100644 --- a/engine/runtime/src/boxed.rs +++ b/engine/runtime/src/boxed.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::types::*; +use random::Rng; use zeroize::Zeroize; use core::{ @@ -13,8 +14,8 @@ use core::{ }; use libsodium_sys::{ - sodium_allocarray, sodium_free, sodium_init, sodium_mlock, sodium_mprotect_noaccess, sodium_mprotect_readonly, - sodium_mprotect_readwrite, + sodium_allocarray, sodium_free, sodium_init, sodium_memzero, sodium_mlock, sodium_mprotect_noaccess, + sodium_mprotect_readonly, sodium_mprotect_readwrite, }; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -145,10 +146,40 @@ impl Boxed { if unsafe { sodium_init() == -1 } { panic!("Failed to initialize libsodium") } + let mut rng = random::thread_rng(); + + // TESTING + let chunk_a = unsafe { + let size = rng.gen_range(0x1000..0xFFFFFF); + let random_length = 1; + let ptr = sodium_allocarray(random_length, size); + sodium_memzero(ptr, size); + ptr + }; + + // END TESTING let ptr = NonNull::new(unsafe { sodium_allocarray(len, mem::size_of::()) as *mut _ }) .expect("Failed to allocate memory"); + // TESTING + unsafe { + sodium_free(chunk_a); + } + + let chunk_a = unsafe { + let size = rng.gen_range(0x1000..0xFFFFFF); + let random_length = 1; + let ptr = sodium_allocarray(random_length, size); + sodium_memzero(ptr, size); + ptr + }; + + unsafe { + sodium_free(chunk_a); + } + // END TESTING + Self { ptr, len, @@ -205,6 +236,13 @@ impl Boxed { fn is_locked(&self) -> bool { self.prot.get() == Prot::NoAccess } + + #[cfg(test)] + #[allow(dead_code)] + /// Returns the address of the pointer to the data + pub fn get_ptr_address(&self) -> usize { + self.ptr.as_ptr() as *const _ as usize + } } impl Boxed { diff --git a/engine/runtime/src/lib.rs b/engine/runtime/src/lib.rs index d3f44a483..dcbbbfe7a 100644 --- a/engine/runtime/src/lib.rs +++ b/engine/runtime/src/lib.rs @@ -39,6 +39,9 @@ pub enum MemoryError { #[error("Failed to allocate memory ({0})")] Allocation(String), + + #[error("Intended operation failed: ({0})")] + Operation(String), } /// A simple trait to force the types to call `zeroize()` when dropping diff --git a/engine/runtime/src/memories/buffer.rs b/engine/runtime/src/memories/buffer.rs index 56584e1c5..2b32dcab1 100644 --- a/engine/runtime/src/memories/buffer.rs +++ b/engine/runtime/src/memories/buffer.rs @@ -23,7 +23,7 @@ use serde::{ /// This shall always be short lived #[derive(Clone, Eq)] pub struct Buffer { - boxed: Boxed, // the boxed type of current GuardedVec + boxed: Boxed, // the boxed type of current GuardedVec, } pub struct Ref<'a, T: Bytes> { @@ -63,6 +63,13 @@ impl Buffer { pub fn borrow_mut(&mut self) -> RefMut<'_, T> { RefMut::new(&mut self.boxed) } + + #[cfg(test)] + #[allow(dead_code)] + /// Returns the address of the pointer to the data + pub fn get_ptr_address(&self) -> usize { + self.boxed.get_ptr_address() + } } impl Buffer { @@ -82,7 +89,7 @@ impl Zeroize for Buffer { impl Drop for Buffer { fn drop(&mut self) { - self.boxed.zeroize() + self.boxed.zeroize(); } } diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 5b6f1d032..ba5def1b4 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -113,7 +113,7 @@ impl Frag { where T: Default, { - Self::alloc2(strategy, 0xFFFF) + Self::alloc2(strategy, 0xFFFFF) } } diff --git a/engine/runtime/src/memories/noncontiguous_memory.rs b/engine/runtime/src/memories/noncontiguous_memory.rs index 8d24ff7cd..3138ad559 100644 --- a/engine/runtime/src/memories/noncontiguous_memory.rs +++ b/engine/runtime/src/memories/noncontiguous_memory.rs @@ -1,6 +1,9 @@ -// Copyright 2020-2021 IOTA Stiftung +// Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +// TODO: +// - replace thread based shard refresh with guard type return and functional refresh + use crate::{ locked_memory::LockedMemory, memories::{buffer::Buffer, file_memory::FileMemory, ram_memory::RamMemory}, @@ -12,6 +15,7 @@ use core::{ fmt::{self, Debug, Formatter}, marker::PhantomData, }; + // use crypto::hashes::sha; use crypto::hashes::{blake2b, Digest}; use zeroize::Zeroize; @@ -61,6 +65,9 @@ impl LockedMemory for NonContiguousMemory { /// Unlocks the memory and returns an unlocked Buffer /// To retrieve secret value you xor the hash contained in shard1 with value in shard2 fn unlock(&self) -> Result, MemoryError> { + // refresh shard before unlock + self.refresh()?; + let data1 = blake2b::Blake2b256::digest(&self.get_buffer_from_shard1().borrow()); let data = match &self.shard2 { @@ -75,6 +82,7 @@ impl LockedMemory for NonContiguousMemory { x } }; + Ok(Buffer::alloc(&data, NC_DATA_SIZE)) } } @@ -90,7 +98,9 @@ impl NonContiguousMemory { let digest = xor(&digest, payload, NC_DATA_SIZE); let ram1 = RamMemory::alloc(&random, NC_DATA_SIZE)?; + let shard1 = RamShard(ram1); + let shard2 = match config { RamAndFile => { let fmem = FileMemory::alloc(&digest, NC_DATA_SIZE)?; @@ -106,11 +116,15 @@ impl NonContiguousMemory { } }; - Ok(NonContiguousMemory { shard1, shard2, config }) + let mem = NonContiguousMemory { shard1, shard2, config }; + + Ok(mem) } fn get_buffer_from_shard1(&self) -> Buffer { - match &self.shard1 { + let shard1 = &self.shard1; + + match shard1 { RamShard(ram) => ram.unlock().expect("Failed to retrieve buffer from Ram shard"), _ => unreachable!("{}", IMPOSSIBLE_CASE), } @@ -119,12 +133,14 @@ impl NonContiguousMemory { // Refresh the shards to increase security, may be called every _n_ seconds or // punctually #[allow(dead_code)] - fn refresh(self) -> Result { + fn refresh(&self) -> Result { let random = random_vec(NC_DATA_SIZE); // Refresh shard1 let buf_of_old_shard1 = self.get_buffer_from_shard1(); + let data_of_old_shard1 = &buf_of_old_shard1.borrow(); + let new_data1 = xor(data_of_old_shard1, &random, NC_DATA_SIZE); let new_shard1 = RamShard(RamMemory::alloc(&new_data1, NC_DATA_SIZE)?); @@ -147,12 +163,33 @@ impl NonContiguousMemory { } }; - Ok(NonContiguousMemory { + Ok(Self { + config: self.config.clone(), shard1: new_shard1, shard2: new_shard2, - config: self.config.clone(), }) } + + /// Returns the memory addresses of the two inner shards. + /// + /// This is for testing purposes only, and is intended to work with `NCConfig::FullRam` + /// only. + #[cfg(test)] + pub fn get_ptr_addresses(&self) -> Result<(usize, usize), MemoryError> { + let a = &self.shard1; + let b = &self.shard2; + + if let (MemoryShard::RamShard(a), MemoryShard::RamShard(b)) = (a, b) { + let a_ptr = a.get_ptr_address(); + let b_ptr = b.get_ptr_address(); + + return Ok((a_ptr, b_ptr)); + } + + Err(MemoryError::Allocation( + "Cannot get pointers. Unsupported MemoryShard configuration".to_owned(), + )) + } } impl Debug for NonContiguousMemory { @@ -183,7 +220,7 @@ impl ZeroizeOnDrop for NonContiguousMemory {} impl Drop for NonContiguousMemory { fn drop(&mut self) { - self.zeroize() + self.zeroize(); } } @@ -245,6 +282,7 @@ impl<'de> Deserialize<'de> for NonContiguousMemory { #[cfg(test)] mod tests { + use super::*; #[test] @@ -261,10 +299,10 @@ mod tests { } else { panic!("{}", IMPOSSIBLE_CASE) }; + let updated = ncm.refresh(); + assert!(updated.is_ok()); - let ncm = ncm.refresh(); - assert!(ncm.is_ok()); - let ncm = ncm.unwrap(); + let ncm = updated.unwrap(); let shard1_after_refresh = ncm.get_buffer_from_shard1(); let shard2_after_refresh = if let FileShard(fm) = &ncm.shard2 { @@ -313,10 +351,11 @@ mod tests { let buf = ram1.unlock().unwrap(); assert_ne!(&*buf.borrow(), &data); } + if let FileShard(fm) = &ncm.shard2 { let buf = fm.unlock().unwrap(); assert_ne!(&*buf.borrow(), &data); - } + }; } #[test] @@ -335,6 +374,30 @@ mod tests { if let FileShard(fm) = &ncm.shard2 { assert!(fm.unlock().is_err()); - } + }; + } + + #[test] + fn test_nc_with_alloc() { + use random::Rng; + + let threshold = 0x4000; + let mut payload = [0u8; NC_DATA_SIZE]; + let mut rng = random::thread_rng(); + assert!(rng.try_fill(&mut payload).is_ok(), "Error filling payload bytes"); + + let nc = NonContiguousMemory::alloc(&payload, NC_DATA_SIZE, NCConfig::FullRam); + assert!(nc.is_ok(), "Failed to allocated nc memory"); + + let ptrs = nc.unwrap().get_ptr_addresses(); + assert!(ptrs.is_ok()); + + let (a, b) = ptrs.unwrap(); + let distance = a.abs_diff(b); + assert!( + distance >= threshold, + "Pointer distance below threshold: 0x{:08X}", + distance + ); } } diff --git a/engine/runtime/src/memories/ram_memory.rs b/engine/runtime/src/memories/ram_memory.rs index 0fc0c03ad..5ff1f875b 100644 --- a/engine/runtime/src/memories/ram_memory.rs +++ b/engine/runtime/src/memories/ram_memory.rs @@ -22,7 +22,7 @@ use serde::{ /// This is basically a wrapper for the Buffer type, but the usage /// is different, buffer type are meant for short lived usage while /// RamMemory can store data for longer period of time. -/// Hence data in RamMemory has to be either encyrpted or protected +/// Hence data in RamMemory has to be either encrypted or protected /// behind a scheme #[derive(Clone)] pub struct RamMemory { @@ -42,6 +42,13 @@ impl RamMemory { size, }) } + + #[cfg(test)] + #[allow(dead_code)] + /// Returns the address of the pointer to the data + pub fn get_ptr_address(&self) -> usize { + self.buf.get_ptr_address() + } } impl LockedMemory for RamMemory { diff --git a/engine/runtime/src/utils.rs b/engine/runtime/src/utils.rs index b9cc3078a..f8bb500c4 100644 --- a/engine/runtime/src/utils.rs +++ b/engine/runtime/src/utils.rs @@ -1,7 +1,7 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use random::{distributions::Alphanumeric, thread_rng, Rng}; +use random::{distributions::Alphanumeric, thread_rng, Rng, RngCore}; pub fn xor(payload: &[u8], noise: &[u8], size: usize) -> Vec { let mut data = vec![0u8; size]; @@ -13,10 +13,9 @@ pub fn xor(payload: &[u8], noise: &[u8], size: usize) -> Vec { pub fn random_vec(size: usize) -> Vec { let mut rng = thread_rng(); - let mut v = vec![0; size]; - for x in v.iter_mut() { - *x = rng.gen(); - } + let mut v = vec![0u8; size]; + rng.fill_bytes(&mut v); + v } diff --git a/engine/runtime/tests/alloc_tests.rs b/engine/runtime/tests/alloc_tests.rs index a814ab084..6226ac0e0 100644 --- a/engine/runtime/tests/alloc_tests.rs +++ b/engine/runtime/tests/alloc_tests.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use log::*; + use runtime::{ memories::frag::{Frag, FragStrategy}, MemoryError, diff --git a/engine/runtime/tests/locked_memory_tests.rs b/engine/runtime/tests/locked_memory_tests.rs index d35a89a6a..7a239efc7 100644 --- a/engine/runtime/tests/locked_memory_tests.rs +++ b/engine/runtime/tests/locked_memory_tests.rs @@ -125,6 +125,8 @@ fn test_clone(lm: impl LockedMemory, size: usize) { let buf_clone = buf_clone.unwrap(); assert_eq!(*buf.borrow(), *buf_clone.borrow()); + // drop(buf); // check, if locks are being released + // Update the clone with a new value let new_data = random_vec(size); let new_buf = Buffer::alloc(&new_data, size); From 74f0527e3326783e1ffa7ab636dd33d682551776 Mon Sep 17 00:00:00 2001 From: Matthias Kandora Date: Tue, 17 May 2022 14:41:30 +0200 Subject: [PATCH 25/46] fix: reduce minimum distance --- engine/runtime/src/memories/frag.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index ba5def1b4..5b6f1d032 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -113,7 +113,7 @@ impl Frag { where T: Default, { - Self::alloc2(strategy, 0xFFFFF) + Self::alloc2(strategy, 0xFFFF) } } From d5fc82296b89dc3a353480a98022f026137666aa Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Wed, 25 May 2022 10:49:34 +0200 Subject: [PATCH 26/46] Refresh the shards after unlocking rather than before --- .../runtime/src/memories/noncontiguous_memory.rs | 15 +++++++++------ engine/runtime/tests/serialization_tests.rs | 2 +- engine/src/vault/crypto_box.rs | 3 --- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/engine/runtime/src/memories/noncontiguous_memory.rs b/engine/runtime/src/memories/noncontiguous_memory.rs index 3138ad559..9652d24a2 100644 --- a/engine/runtime/src/memories/noncontiguous_memory.rs +++ b/engine/runtime/src/memories/noncontiguous_memory.rs @@ -30,6 +30,9 @@ static IMPOSSIBLE_CASE: &str = "NonContiguousMemory: this case should not happen // Currently we only support data of 32 bytes in noncontiguous memory pub const NC_DATA_SIZE: usize = 32; +// Temporary, we currently only use non contiguous with the two shards in RAM +pub const NC_CONFIGURATION: NCConfig = FullRam; + #[derive(Debug, PartialEq, Eq, Clone)] pub enum NCConfig { FullFile, @@ -65,9 +68,6 @@ impl LockedMemory for NonContiguousMemory { /// Unlocks the memory and returns an unlocked Buffer /// To retrieve secret value you xor the hash contained in shard1 with value in shard2 fn unlock(&self) -> Result, MemoryError> { - // refresh shard before unlock - self.refresh()?; - let data1 = blake2b::Blake2b256::digest(&self.get_buffer_from_shard1().borrow()); let data = match &self.shard2 { @@ -83,6 +83,9 @@ impl LockedMemory for NonContiguousMemory { } }; + // Refresh shards positions after unlocking them + self.refresh()?; + Ok(Buffer::alloc(&data, NC_DATA_SIZE)) } } @@ -263,8 +266,7 @@ impl<'de> Visitor<'de> for NonContiguousMemoryVisitor { seq.push(e); } - // TODO we need to get back the previous config - let seq = NonContiguousMemory::alloc(seq.as_slice(), seq.len(), FullRam) + let seq = NonContiguousMemory::alloc(seq.as_slice(), seq.len(), NC_CONFIGURATION) .expect("Failed to allocate NonContiguousMemory during deserialization"); Ok(seq) @@ -381,7 +383,8 @@ mod tests { fn test_nc_with_alloc() { use random::Rng; - let threshold = 0x4000; + // Usual size for a page + let threshold = 0x1000; let mut payload = [0u8; NC_DATA_SIZE]; let mut rng = random::thread_rng(); assert!(rng.try_fill(&mut payload).is_ok(), "Error filling payload bytes"); diff --git a/engine/runtime/tests/serialization_tests.rs b/engine/runtime/tests/serialization_tests.rs index 92404b936..cb48715ae 100644 --- a/engine/runtime/tests/serialization_tests.rs +++ b/engine/runtime/tests/serialization_tests.rs @@ -45,7 +45,7 @@ fn serialize_deserialize_ok() { } #[test] -// For backward compatibility all the types should return same kind of data +// For compatibility all the types should return same kind of data fn serialized_data_equal() { let data = random_vec(NC_DATA_SIZE); let buf = Buffer::alloc(&data, NC_DATA_SIZE); diff --git a/engine/src/vault/crypto_box.rs b/engine/src/vault/crypto_box.rs index eef4bb948..e97b8fa1e 100644 --- a/engine/src/vault/crypto_box.rs +++ b/engine/src/vault/crypto_box.rs @@ -12,9 +12,6 @@ use std::{ marker::PhantomData, }; -// We store key in non contiguous memory spread in ram -const NC_CONFIGURATION: NCConfig = NCConfig::FullRam; - /// A provider interface between the vault and a crypto box. See libsodium's [secretbox](https://libsodium.gitbook.io/doc/secret-key_cryptography/secretbox) for an example. pub trait BoxProvider: 'static + Sized + Ord + PartialOrd { type Error: Debug; From 62c9e2b660a195112ff4a15eadac16523b7134cd Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Fri, 3 Jun 2022 02:51:41 +0200 Subject: [PATCH 27/46] Refresh non-contiguous memory shards after every unlocking --- .../src/memories/noncontiguous_memory.rs | 87 ++++++++++++------- 1 file changed, 58 insertions(+), 29 deletions(-) diff --git a/engine/runtime/src/memories/noncontiguous_memory.rs b/engine/runtime/src/memories/noncontiguous_memory.rs index 9652d24a2..5fbf63bbf 100644 --- a/engine/runtime/src/memories/noncontiguous_memory.rs +++ b/engine/runtime/src/memories/noncontiguous_memory.rs @@ -25,7 +25,10 @@ use serde::{ ser::{Serialize, Serializer}, }; +use std::cell::RefCell; + static IMPOSSIBLE_CASE: &str = "NonContiguousMemory: this case should not happen if allocated properly"; +static REFRESH_ERROR: &str = "NonContiguousMemory: Error while refreshing the shards"; // Currently we only support data of 32 bytes in noncontiguous memory pub const NC_DATA_SIZE: usize = 32; @@ -54,8 +57,8 @@ use MemoryShard::*; /// store keys hence the size of the data depends on the chosen box provider #[derive(Clone)] pub struct NonContiguousMemory { - shard1: MemoryShard, - shard2: MemoryShard, + shard1: RefCell, + shard2: RefCell, config: NCConfig, } @@ -70,7 +73,7 @@ impl LockedMemory for NonContiguousMemory { fn unlock(&self) -> Result, MemoryError> { let data1 = blake2b::Blake2b256::digest(&self.get_buffer_from_shard1().borrow()); - let data = match &self.shard2 { + let data = match &*self.shard2.borrow() { RamShard(ram2) => { let buf = ram2.unlock()?; let x = xor(&data1, &buf.borrow(), NC_DATA_SIZE); @@ -83,8 +86,8 @@ impl LockedMemory for NonContiguousMemory { } }; - // Refresh shards positions after unlocking them - self.refresh()?; + // Refresh the shards after each use + self.refresh().expect(REFRESH_ERROR); Ok(Buffer::alloc(&data, NC_DATA_SIZE)) } @@ -119,13 +122,17 @@ impl NonContiguousMemory { } }; - let mem = NonContiguousMemory { shard1, shard2, config }; + let mem = NonContiguousMemory { + shard1: RefCell::new(shard1), + shard2: RefCell::new(shard2), + config, + }; Ok(mem) } fn get_buffer_from_shard1(&self) -> Buffer { - let shard1 = &self.shard1; + let shard1 = &*self.shard1.borrow(); match shard1 { RamShard(ram) => ram.unlock().expect("Failed to retrieve buffer from Ram shard"), @@ -136,7 +143,7 @@ impl NonContiguousMemory { // Refresh the shards to increase security, may be called every _n_ seconds or // punctually #[allow(dead_code)] - fn refresh(&self) -> Result { + fn refresh(&self) -> Result<(), MemoryError> { let random = random_vec(NC_DATA_SIZE); // Refresh shard1 @@ -150,7 +157,7 @@ impl NonContiguousMemory { let hash_of_old_shard1 = blake2b::Blake2b256::digest(data_of_old_shard1); let hash_of_new_shard1 = blake2b::Blake2b256::digest(&new_data1); - let new_shard2 = match &self.shard2 { + let new_shard2 = match &*self.shard2.borrow() { RamShard(ram2) => { let buf = ram2.unlock()?; let new_data2 = xor(&buf.borrow(), &hash_of_old_shard1, NC_DATA_SIZE); @@ -166,11 +173,10 @@ impl NonContiguousMemory { } }; - Ok(Self { - config: self.config.clone(), - shard1: new_shard1, - shard2: new_shard2, - }) + self.shard1.replace(new_shard1); + self.shard2.replace(new_shard2); + + Ok(()) } /// Returns the memory addresses of the two inner shards. @@ -179,8 +185,8 @@ impl NonContiguousMemory { /// only. #[cfg(test)] pub fn get_ptr_addresses(&self) -> Result<(usize, usize), MemoryError> { - let a = &self.shard1; - let b = &self.shard2; + let a = &*self.shard1.borrow(); + let b = &*self.shard2.borrow(); if let (MemoryShard::RamShard(a), MemoryShard::RamShard(b)) = (a, b) { let a_ptr = a.get_ptr_address(); @@ -213,8 +219,8 @@ impl Zeroize for MemoryShard { impl Zeroize for NonContiguousMemory { fn zeroize(&mut self) { - self.shard1.zeroize(); - self.shard2.zeroize(); + self.shard1.borrow_mut().zeroize(); + self.shard2.borrow_mut().zeroize(); self.config = FullRam; } } @@ -296,18 +302,16 @@ mod tests { let ncm = ncm.unwrap(); let shard1_before_refresh = ncm.get_buffer_from_shard1(); - let shard2_before_refresh = if let FileShard(fm) = &ncm.shard2 { + let shard2_before_refresh = if let FileShard(fm) = &*ncm.shard2.borrow() { fm.unlock().unwrap() } else { panic!("{}", IMPOSSIBLE_CASE) }; - let updated = ncm.refresh(); - assert!(updated.is_ok()); - let ncm = updated.unwrap(); + assert!(ncm.refresh().is_ok()); let shard1_after_refresh = ncm.get_buffer_from_shard1(); - let shard2_after_refresh = if let FileShard(fm) = &ncm.shard2 { + let shard2_after_refresh = if let FileShard(fm) = &*ncm.shard2.borrow() { fm.unlock().unwrap() } else { panic!("{}", IMPOSSIBLE_CASE) @@ -333,11 +337,11 @@ mod tests { assert!(ncm.is_ok()); let ncm = ncm.unwrap(); - if let RamShard(ram1) = &ncm.shard1 { + if let RamShard(ram1) = &*ncm.shard1.borrow() { let buf = ram1.unlock().unwrap(); assert_ne!(&*buf.borrow(), &data); } - if let RamShard(ram2) = &ncm.shard2 { + if let RamShard(ram2) = &*ncm.shard2.borrow() { let buf = ram2.unlock().unwrap(); assert_ne!(&*buf.borrow(), &data); } @@ -349,12 +353,12 @@ mod tests { assert!(ncm.is_ok()); let ncm = ncm.unwrap(); - if let RamShard(ram1) = &ncm.shard1 { + if let RamShard(ram1) = &*ncm.shard1.borrow() { let buf = ram1.unlock().unwrap(); assert_ne!(&*buf.borrow(), &data); } - if let FileShard(fm) = &ncm.shard2 { + if let FileShard(fm) = &*ncm.shard2.borrow() { let buf = fm.unlock().unwrap(); assert_ne!(&*buf.borrow(), &data); }; @@ -370,11 +374,11 @@ mod tests { let mut ncm = ncm.unwrap(); ncm.zeroize(); - if let RamShard(ram1) = &ncm.shard1 { + if let RamShard(ram1) = &*ncm.shard1.borrow() { assert!(ram1.unlock().is_err()); } - if let FileShard(fm) = &ncm.shard2 { + if let FileShard(fm) = &*ncm.shard2.borrow() { assert!(fm.unlock().is_err()); }; } @@ -403,4 +407,29 @@ mod tests { distance ); } + + // This test is relevant only if the implemented policy is to refresh shards every time we unlock NC memory + #[test] + fn test_refresh_on_unlock() { + use random::Rng; + let mut payload = [0u8; NC_DATA_SIZE]; + let mut rng = random::thread_rng(); + assert!(rng.try_fill(&mut payload).is_ok(), "Error filling payload bytes"); + + let nc = NonContiguousMemory::alloc(&payload, NC_DATA_SIZE, NCConfig::FullRam); + assert!(nc.is_ok(), "Failed to allocated nc memory"); + let nc = nc.unwrap(); + + let ptrs = nc.get_ptr_addresses(); + assert!(ptrs.is_ok()); + let (a, b) = ptrs.unwrap(); + + assert!(nc.unlock().is_ok()); + let ptrs = nc.get_ptr_addresses(); + assert!(ptrs.is_ok()); + let (new_a, new_b) = ptrs.unwrap(); + + assert_ne!(a, new_a); + assert_ne!(b, new_b); + } } From cd9037c25796082bb51dac72347de94f813357a3 Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Fri, 3 Jun 2022 18:30:56 +0200 Subject: [PATCH 28/46] Add mutex in the shards of noncontiguous memory --- engine/runtime/src/lib.rs | 3 + .../src/memories/noncontiguous_memory.rs | 71 ++++++++++++------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/engine/runtime/src/lib.rs b/engine/runtime/src/lib.rs index dcbbbfe7a..d2885c707 100644 --- a/engine/runtime/src/lib.rs +++ b/engine/runtime/src/lib.rs @@ -28,6 +28,9 @@ pub enum MemoryError { #[error("Illegal non-contiguous size")] NCSizeNotAllowed, + #[error("Error while refreshing non-contiguous memory")] + NCRefreshError, + #[error("Lock unavailable")] LockNotAvailable, diff --git a/engine/runtime/src/memories/noncontiguous_memory.rs b/engine/runtime/src/memories/noncontiguous_memory.rs index 5fbf63bbf..03c45d5c1 100644 --- a/engine/runtime/src/memories/noncontiguous_memory.rs +++ b/engine/runtime/src/memories/noncontiguous_memory.rs @@ -25,10 +25,10 @@ use serde::{ ser::{Serialize, Serializer}, }; -use std::cell::RefCell; +use std::{cell::RefCell, sync::Mutex}; static IMPOSSIBLE_CASE: &str = "NonContiguousMemory: this case should not happen if allocated properly"; -static REFRESH_ERROR: &str = "NonContiguousMemory: Error while refreshing the shards"; +static POISONED_LOCK: &str = "NonContiguousMemory potentially in an unsafe state"; // Currently we only support data of 32 bytes in noncontiguous memory pub const NC_DATA_SIZE: usize = 32; @@ -55,13 +55,24 @@ use MemoryShard::*; /// NonContiguousMemory only works on data which size corresponds to the hash primitive we use. In our case we use it to /// store keys hence the size of the data depends on the chosen box provider -#[derive(Clone)] pub struct NonContiguousMemory { - shard1: RefCell, - shard2: RefCell, + shard1: Mutex>, + shard2: Mutex>, config: NCConfig, } +impl Clone for NonContiguousMemory { + fn clone(&self) -> Self { + let mut1 = self.shard1.lock().expect(POISONED_LOCK); + let mut2 = self.shard2.lock().expect(POISONED_LOCK); + NonContiguousMemory { + shard1: Mutex::new(mut1.clone()), + shard2: Mutex::new(mut2.clone()), + config: self.config.clone(), + } + } +} + impl LockedMemory for NonContiguousMemory { /// Locks the memory and possibly reallocates fn update(self, payload: Buffer, size: usize) -> Result { @@ -73,7 +84,8 @@ impl LockedMemory for NonContiguousMemory { fn unlock(&self) -> Result, MemoryError> { let data1 = blake2b::Blake2b256::digest(&self.get_buffer_from_shard1().borrow()); - let data = match &*self.shard2.borrow() { + let mut2 = self.shard2.lock().expect(POISONED_LOCK); + let data = match &*mut2.borrow() { RamShard(ram2) => { let buf = ram2.unlock()?; let x = xor(&data1, &buf.borrow(), NC_DATA_SIZE); @@ -85,9 +97,10 @@ impl LockedMemory for NonContiguousMemory { x } }; + drop(mut2); // Refresh the shards after each use - self.refresh().expect(REFRESH_ERROR); + self.refresh()?; Ok(Buffer::alloc(&data, NC_DATA_SIZE)) } @@ -123,8 +136,8 @@ impl NonContiguousMemory { }; let mem = NonContiguousMemory { - shard1: RefCell::new(shard1), - shard2: RefCell::new(shard2), + shard1: Mutex::new(RefCell::new(shard1)), + shard2: Mutex::new(RefCell::new(shard2)), config, }; @@ -132,7 +145,8 @@ impl NonContiguousMemory { } fn get_buffer_from_shard1(&self) -> Buffer { - let shard1 = &*self.shard1.borrow(); + let mut1 = self.shard1.lock().expect(POISONED_LOCK); + let shard1 = &*mut1.borrow(); match shard1 { RamShard(ram) => ram.unlock().expect("Failed to retrieve buffer from Ram shard"), @@ -142,7 +156,6 @@ impl NonContiguousMemory { // Refresh the shards to increase security, may be called every _n_ seconds or // punctually - #[allow(dead_code)] fn refresh(&self) -> Result<(), MemoryError> { let random = random_vec(NC_DATA_SIZE); @@ -157,7 +170,8 @@ impl NonContiguousMemory { let hash_of_old_shard1 = blake2b::Blake2b256::digest(data_of_old_shard1); let hash_of_new_shard1 = blake2b::Blake2b256::digest(&new_data1); - let new_shard2 = match &*self.shard2.borrow() { + let mut2 = self.shard2.lock().expect(POISONED_LOCK); + let new_shard2 = match &*mut2.borrow() { RamShard(ram2) => { let buf = ram2.unlock()?; let new_data2 = xor(&buf.borrow(), &hash_of_old_shard1, NC_DATA_SIZE); @@ -173,8 +187,9 @@ impl NonContiguousMemory { } }; - self.shard1.replace(new_shard1); - self.shard2.replace(new_shard2); + let mut1 = self.shard1.lock().expect(POISONED_LOCK); + mut1.replace(new_shard1); + mut2.replace(new_shard2); Ok(()) } @@ -185,8 +200,10 @@ impl NonContiguousMemory { /// only. #[cfg(test)] pub fn get_ptr_addresses(&self) -> Result<(usize, usize), MemoryError> { - let a = &*self.shard1.borrow(); - let b = &*self.shard2.borrow(); + let muta = self.shard1.lock().expect(POISONED_LOCK); + let mutb = self.shard2.lock().expect(POISONED_LOCK); + let a = &*muta.borrow(); + let b = &*mutb.borrow(); if let (MemoryShard::RamShard(a), MemoryShard::RamShard(b)) = (a, b) { let a_ptr = a.get_ptr_address(); @@ -219,8 +236,10 @@ impl Zeroize for MemoryShard { impl Zeroize for NonContiguousMemory { fn zeroize(&mut self) { - self.shard1.borrow_mut().zeroize(); - self.shard2.borrow_mut().zeroize(); + let mut1 = self.shard1.lock().expect(POISONED_LOCK); + let mut2 = self.shard2.lock().expect(POISONED_LOCK); + mut1.borrow_mut().zeroize(); + mut2.borrow_mut().zeroize(); self.config = FullRam; } } @@ -302,7 +321,7 @@ mod tests { let ncm = ncm.unwrap(); let shard1_before_refresh = ncm.get_buffer_from_shard1(); - let shard2_before_refresh = if let FileShard(fm) = &*ncm.shard2.borrow() { + let shard2_before_refresh = if let FileShard(fm) = &*ncm.shard2.lock().expect(POISONED_LOCK).borrow() { fm.unlock().unwrap() } else { panic!("{}", IMPOSSIBLE_CASE) @@ -311,7 +330,7 @@ mod tests { assert!(ncm.refresh().is_ok()); let shard1_after_refresh = ncm.get_buffer_from_shard1(); - let shard2_after_refresh = if let FileShard(fm) = &*ncm.shard2.borrow() { + let shard2_after_refresh = if let FileShard(fm) = &*ncm.shard2.lock().expect(POISONED_LOCK).borrow() { fm.unlock().unwrap() } else { panic!("{}", IMPOSSIBLE_CASE) @@ -337,11 +356,11 @@ mod tests { assert!(ncm.is_ok()); let ncm = ncm.unwrap(); - if let RamShard(ram1) = &*ncm.shard1.borrow() { + if let RamShard(ram1) = &*ncm.shard1.lock().expect(POISONED_LOCK).borrow() { let buf = ram1.unlock().unwrap(); assert_ne!(&*buf.borrow(), &data); } - if let RamShard(ram2) = &*ncm.shard2.borrow() { + if let RamShard(ram2) = &*ncm.shard2.lock().expect(POISONED_LOCK).borrow() { let buf = ram2.unlock().unwrap(); assert_ne!(&*buf.borrow(), &data); } @@ -353,12 +372,12 @@ mod tests { assert!(ncm.is_ok()); let ncm = ncm.unwrap(); - if let RamShard(ram1) = &*ncm.shard1.borrow() { + if let RamShard(ram1) = &*ncm.shard1.lock().expect(POISONED_LOCK).borrow() { let buf = ram1.unlock().unwrap(); assert_ne!(&*buf.borrow(), &data); } - if let FileShard(fm) = &*ncm.shard2.borrow() { + if let FileShard(fm) = &*ncm.shard2.lock().expect(POISONED_LOCK).borrow() { let buf = fm.unlock().unwrap(); assert_ne!(&*buf.borrow(), &data); }; @@ -374,11 +393,11 @@ mod tests { let mut ncm = ncm.unwrap(); ncm.zeroize(); - if let RamShard(ram1) = &*ncm.shard1.borrow() { + if let RamShard(ram1) = &*ncm.shard1.lock().expect(POISONED_LOCK).borrow() { assert!(ram1.unlock().is_err()); } - if let FileShard(fm) = &*ncm.shard2.borrow() { + if let FileShard(fm) = &*ncm.shard2.lock().expect(POISONED_LOCK).borrow() { assert!(fm.unlock().is_err()); }; } From be7bdfc2b173b55f89e7e2e82f3ad0490dfebf93 Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Fri, 3 Jun 2022 19:29:44 +0200 Subject: [PATCH 29/46] Clean the code and slightly improve error handling --- engine/src/vault.rs | 2 +- engine/src/vault/crypto_box.rs | 44 +++++++--------------------------- 2 files changed, 10 insertions(+), 36 deletions(-) diff --git a/engine/src/vault.rs b/engine/src/vault.rs index e03526f90..cfbc0c044 100644 --- a/engine/src/vault.rs +++ b/engine/src/vault.rs @@ -23,7 +23,7 @@ pub mod view; pub use crate::vault::{ base64::{Base64Decodable, Base64Encodable}, - crypto_box::{BoxProvider, Decrypt, DecryptError, Encrypt, Key, NCDecrypt, NCEncrypt, NCKey}, + crypto_box::{BoxProvider, Decrypt, DecryptError, Encrypt, Key, NCKey}, types::utils::{BlobId, ChainId, ClientId, Id, InvalidLength, RecordHint, RecordId, VaultId}, view::{DbView, RecordError, VaultError}, }; diff --git a/engine/src/vault/crypto_box.rs b/engine/src/vault/crypto_box.rs index e97b8fa1e..f752fb7a3 100644 --- a/engine/src/vault/crypto_box.rs +++ b/engine/src/vault/crypto_box.rs @@ -173,7 +173,7 @@ impl NCKey { T::box_key_len(), NC_CONFIGURATION, ) - .expect("Failed to generate non contiguous memory for key") + .unwrap_or_else(|e| panic!("{}", e)) }, _box_provider: PhantomData, } @@ -186,7 +186,7 @@ impl NCKey { if key.len() == T::box_key_len() { Some(Self { key: NonContiguousMemory::alloc(key.as_slice(), T::box_key_len(), NC_CONFIGURATION) - .expect("Failed to generate non contiguous memory for key"), + .unwrap_or_else(|e| panic!("{}", e)), _box_provider: PhantomData, }) } else { @@ -196,7 +196,7 @@ impl NCKey { pub fn encrypt_key>(&self, data: &Key, ad: AD) -> Result, T::Error> { let key = Key { - key: self.key.unlock().expect("Failed to unlock non contiguous memory"), + key: self.key.unlock().unwrap_or_else(|e| panic!("{}", e)), _box_provider: PhantomData, }; T::box_seal(&key, ad.as_ref(), &*data.key.borrow()) @@ -204,7 +204,7 @@ impl NCKey { pub fn decrypt_key>(&self, data: Vec, ad: AD) -> Result, DecryptError> { let key = Key { - key: self.key.unlock().expect("Failed to unlock non contiguous memory"), + key: self.key.unlock().unwrap_or_else(|e| panic!("{}", e)), _box_provider: PhantomData, }; let opened = T::box_open(&key, ad.as_ref(), &data).map_err(DecryptError::Provider)?; @@ -232,8 +232,8 @@ impl Eq for NCKey {} impl PartialEq for NCKey { fn eq(&self, other: &Self) -> bool { - let buf1 = self.key.unlock().expect("Failed to unlock non-contiguous memory"); - let buf2 = other.key.unlock().expect("Failed to unlock non-contiguous memory"); + let buf1 = self.key.unlock().unwrap_or_else(|e| panic!("{}", e)); + let buf2 = other.key.unlock().unwrap_or_else(|e| panic!("{}", e)); buf1 == buf2 && self._box_provider == other._box_provider } } @@ -246,8 +246,8 @@ impl PartialOrd for NCKey { impl Ord for NCKey { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - let buf1 = self.key.unlock().expect("Failed to unlock non-contiguous memory"); - let buf2 = other.key.unlock().expect("Failed to unlock non-contiguous memory"); + let buf1 = self.key.unlock().unwrap_or_else(|e| panic!("{}", e)); + let buf2 = other.key.unlock().unwrap_or_else(|e| panic!("{}", e)); let b = buf1.borrow().cmp(&*buf2.borrow()); b } @@ -255,7 +255,7 @@ impl Ord for NCKey { impl Hash for NCKey { fn hash(&self, state: &mut H) { - let buf = self.key.unlock().expect("Failed to unlock non-contiguous memory"); + let buf = self.key.unlock().unwrap_or_else(|e| panic!("{}", e)); buf.borrow().hash(state); self._box_provider.hash(state); } @@ -266,29 +266,3 @@ impl Debug for NCKey { write!(f, "KeyData") } } - -/// trait for encryptable data. Allows the data to be encrypted. -pub trait NCEncrypt>>: AsRef<[u8]> { - /// encrypts a raw data and creates a type T from the ciphertext - fn encrypt>(&self, key: &NCKey, ad: AD) -> Result { - let key = Key { - key: key.key.unlock().expect("Failed to unlock non contiguous memory"), - _box_provider: PhantomData, - }; - let sealed = B::box_seal(&key, ad.as_ref(), self.as_ref())?; - Ok(T::from(sealed)) - } -} - -/// Trait for decryptable data. Allows the data to be decrypted. -pub trait NCDecrypt>>: AsRef<[u8]> { - /// decrypts raw data and creates a new type T from the plaintext - fn decrypt>(&self, key: &NCKey

, ad: AD) -> Result> { - let key = Key { - key: key.key.unlock().expect("Failed to unlock non contiguous memory"), - _box_provider: PhantomData, - }; - let opened = P::box_open(&key, ad.as_ref(), self.as_ref()).map_err(DecryptError::Provider)?; - T::try_from(opened).map_err(|_| DecryptError::Invalid) - } -} From 3d326092635232cc2f1232dfbb40f5b1a05d5fb6 Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Thu, 16 Jun 2022 19:25:17 +0200 Subject: [PATCH 30/46] Rework of frag.rs on linux - return a `Frag` smart pointer - add deallocation function - fix test bug --- engine/Cargo.toml | 4 + .../benches/{benchmark.rs => engine_bench.rs} | 16 +- engine/runtime/src/memories/frag.rs | 282 ++++++++++++------ engine/runtime/tests/alloc_tests.rs | 21 +- 4 files changed, 218 insertions(+), 105 deletions(-) rename engine/benches/{benchmark.rs => engine_bench.rs} (84%) diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 3691d215b..89a828a16 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -50,3 +50,7 @@ json = "0.12" [dev-dependencies.stronghold-utils] path = "../utils" version = "0.3" + +[[bench]] +name = "engine_bench" +harness = false diff --git a/engine/benches/benchmark.rs b/engine/benches/engine_bench.rs similarity index 84% rename from engine/benches/benchmark.rs rename to engine/benches/engine_bench.rs index c48ee8333..02c79a6d3 100644 --- a/engine/benches/benchmark.rs +++ b/engine/benches/engine_bench.rs @@ -126,7 +126,13 @@ fn bench_allocate_direct(c: &mut Criterion) { c.bench_function("Allocate memory direct", |b| { b.iter(|| { - let _ = Frag::alloc::(FragStrategy::Direct); + let strat = FragStrategy::Direct; + if let Ok((m1, m2)) = Frag::::alloc(strat) { + Frag::dealloc(m1).expect("error while deallocating"); + Frag::dealloc(m2).expect("error while deallocating"); + } else { + panic!("error while allocating memory"); + } }); }); } @@ -149,7 +155,13 @@ fn bench_allocate_mapped(c: &mut Criterion) { c.bench_function("Allocate memory mapped", |b| { b.iter(|| { - let _ = Frag::alloc::(FragStrategy::Map); + let strat = FragStrategy::Map; + if let Ok((m1, m2)) = Frag::::alloc(strat) { + Frag::dealloc(m1).expect("error while deallocating"); + Frag::dealloc(m2).expect("error while deallocating"); + } else { + panic!("error while allocating memory"); + } }); }); } diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 5b6f1d032..da617e6f7 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -18,7 +18,11 @@ use crate::MemoryError; use log::*; -use std::{fmt::Debug, ptr::NonNull}; +use std::{ + fmt::Debug, + ops::{Deref, DerefMut}, + ptr::NonNull, +}; /// Fragmenting strategy to allocate memory at random addresses. #[derive(Debug, Clone, Copy)] @@ -34,19 +38,40 @@ pub enum FragStrategy { // ----------------------------------------------------------------------------- /// Custom allocator trait -pub trait Alloc { +pub trait Alloc { type Error; /// Allocates `T`, returns an error if something wrong happened. Takes an /// optional configuration to check against a previous allocation - fn alloc(config: Option) -> Result, Self::Error>; + fn alloc(config: Option) -> Result, Self::Error>; + + /// Deallocate `T`, returns an error if something wrong happened. + fn dealloc(frag: Frag) -> Result<(), Self::Error>; } // ----------------------------------------------------------------------------- /// Frag is being used as control object to load different allocators /// according to their strategy -pub struct Frag; +pub struct Frag { + ptr: NonNull, + strategy: FragStrategy, + info: (*mut libc::c_void, usize), +} + +impl Deref for Frag { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { self.ptr.as_ref() } + } +} + +impl DerefMut for Frag { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { self.ptr.as_mut() } + } +} /// Configuration for the fragmenting allocator pub struct FragConfig { @@ -69,7 +94,7 @@ impl FragConfig { } } -impl Frag { +impl Frag { /// Returns a fragmenting allocator by strategy /// /// # Example @@ -79,10 +104,8 @@ impl Frag { /// /// let object = Frag::by_strategy(FragStrategy::Default).unwrap(); /// ``` - pub fn alloc_single(strategy: FragStrategy, config: Option) -> Result, MemoryError> - where - T: Default, - { + + pub fn alloc_single(strategy: FragStrategy, config: Option) -> Result, MemoryError> { match strategy { FragStrategy::Direct => DirectAlloc::alloc(config), FragStrategy::Map => MemoryMapAlloc::alloc(config), @@ -90,31 +113,43 @@ impl Frag { } /// Tries to allocate two objects of the same type with a minimum distance in memory space. - pub fn alloc2(strategy: FragStrategy, distance: usize) -> Result<(NonNull, NonNull), MemoryError> - where - T: Default, - { - let a = Self::alloc_single::(strategy, None)?; - let b = Self::alloc_single::(strategy, Some(FragConfig::new(&a as *const _ as usize, distance)))?; - unsafe { - let actual_distance = calc_distance(a.as_ref(), b.as_ref()); - if actual_distance < distance { - return Err(MemoryError::Allocation(format!( - "Distance between parts below threshold: 0x{:016X}", - actual_distance - ))); - } + pub fn alloc2(strategy: FragStrategy, distance: usize) -> Result<(Frag, Frag), MemoryError> { + let a = Self::alloc_single(strategy, None)?; + let b = Self::alloc_single(strategy, Some(FragConfig::new(a.ptr.as_ptr() as usize, distance)))?; + + let actual_distance = calc_distance(&*a, &*b); + if actual_distance < distance { + error!( + "Distance between parts below threshold: \nthreshold: 0x{:016X} \nactual_distance: 0x{:016X}", + distance, actual_distance + ); + error!( + "Distance between parts below threshold: \na: 0x{:016x} \nb: 0x{:016x} \ngiven_value: 0x{:016x}", + a.ptr.as_ptr() as usize, + b.ptr.as_ptr() as usize, + &a as *const _ as usize + ); + return Err(MemoryError::Allocation(format!( + "Distance between parts below threshold: 0x{:016X}", + actual_distance + ))); } + info!("Mapped 2 fragments: at {:?} and {:?}", a.ptr, b.ptr); Ok((a, b)) } + /// Tries to allocate two objects of the same type with a default minimum distance in memory space of `0xFFFF`. - pub fn alloc(strategy: FragStrategy) -> Result<(NonNull, NonNull), MemoryError> - where - T: Default, - { + pub fn alloc(strategy: FragStrategy) -> Result<(Frag, Frag), MemoryError> { Self::alloc2(strategy, 0xFFFF) } + + pub fn dealloc(ptr: Frag) -> Result<(), MemoryError> { + match ptr.strategy { + FragStrategy::Direct => DirectAlloc::dealloc(ptr), + FragStrategy::Map => MemoryMapAlloc::dealloc(ptr), + } + } } // ----------------------------------------------------------------------------- @@ -129,7 +164,7 @@ impl Frag { /// use runtime::memories::frag::{Frag, FragStrategy}; /// /// // allocates the object at a random address -/// let object = Frag::alloc::(FragStrategy::Map).unwrap(); +/// let object = Frag::::alloc(FragStrategy::Map).unwrap(); /// ``` #[derive(Default, Clone)] struct MemoryMapAlloc; @@ -141,7 +176,7 @@ where type Error = MemoryError; #[cfg(any(target_os = "linux", target_os = "macos"))] - fn alloc(config: Option) -> Result, Self::Error> { + fn alloc(config: Option) -> Result, Self::Error> { let hr = "-".repeat(20); info!("{0}Mapping Allocator{0}", hr); @@ -172,7 +207,7 @@ where info!("prealloc: desired alloc size 0x{:08X}", desired_alloc_size); // this creates an anonymous mapping zeroed out. - let ptr = libc::mmap( + let c_ptr = libc::mmap( &mut addr as *mut _ as *mut libc::c_void, desired_alloc_size, libc::PROT_READ | libc::PROT_WRITE, @@ -181,17 +216,24 @@ where 0, ); - info!("Preallocated segment addr: {:p}", ptr); + info!("Preallocated segment addr: {:p}", c_ptr); - if ptr == libc::MAP_FAILED { + if c_ptr == libc::MAP_FAILED { warn!("Memory mapping failed"); continue; } if let Some(ref cfg) = config { - let actual_distance = (ptr as usize).abs_diff(cfg.last_address); + let actual_distance = (c_ptr as usize).abs_diff(cfg.last_address); if actual_distance < cfg.min_distance { warn!("New allocation distance to previous allocation is below threshold."); + + // Deallocate used memory + let res = libc::munmap(c_ptr, desired_alloc_size); + if res != 0 { + error!("Failed to munmap"); + return Err(MemoryError::Allocation("Failed to munmap".to_owned())); + } continue; } } @@ -209,7 +251,7 @@ where } } - let ptr = ptr as *mut T; + let ptr = c_ptr as *mut T; if !ptr.is_null() { let t = T::default(); @@ -217,12 +259,29 @@ where info!("Object succesfully written into mem location"); - return Ok(NonNull::new_unchecked(ptr)); + return Ok(Frag { + ptr: NonNull::new_unchecked(ptr), + strategy: FragStrategy::Map, + info: (c_ptr, desired_alloc_size), + }); } } } } + #[cfg(any(target_os = "linux", target_os = "macos"))] + fn dealloc(frag: Frag) -> Result<(), Self::Error> { + // munmap returns 0 on success + unsafe { + let res = libc::munmap(frag.info.0, frag.info.1 as libc::size_t); + if res != 0 { + let os_error = std::io::Error::last_os_error(); + return Err(MemoryError::Allocation(format!("Failed to munmap: {}", os_error))); + } + } + Ok(()) + } + #[cfg(target_os = "windows")] fn alloc(config: Option) -> Result, Self::Error> { use random::{thread_rng, Rng}; @@ -232,54 +291,54 @@ where loop { unsafe { // allocation prelude - { - let r_addr = rng.gen::() >> 4; - - let random_mapping = windows::Win32::System::Memory::CreateFileMappingW( - handle, - std::ptr::null_mut(), - windows::Win32::System::Memory::PAGE_READWRITE, - 0, - r_addr, - windows::core::PCWSTR(std::ptr::null_mut()), - ) - .map_err(|e| MemoryError::Allocation(e.to_string()))?; - - if let Err(e) = last_error() { - return Err(e); - } - - let ptr = windows::Win32::System::Memory::MapViewOfFile( - random_mapping, - windows::Win32::System::Memory::FILE_MAP_ALL_ACCESS, - 0, - 0, - r_addr as usize, - ); - - if let Err(e) = last_error() { - return Err(e); - } - - if let Some(ref cfg) = config { - let actual_distance = (ptr as *const _ as usize).abs_diff(cfg.last_address); - if actual_distance < cfg.min_distance { - warn!( - "New allocation distance to previous allocation is below threshold: {}", - actual_distance - ); - - // remove previous file mapping - if !windows::Win32::System::Memory::UnmapViewOfFile(ptr).as_bool() { - if let Err(e) = last_error() { - return Err(e); - } - } - - continue; - } - } - } + // { + // let r_addr = rng.gen::() >> 4; + + // let random_mapping = windows::Win32::System::Memory::CreateFileMappingW( + // handle, + // std::ptr::null_mut(), + // windows::Win32::System::Memory::PAGE_READWRITE, + // 0, + // r_addr, + // windows::core::PCWSTR(std::ptr::null_mut()), + // ) + // .map_err(|e| MemoryError::Allocation(e.to_string()))?; + + // if let Err(e) = last_error() { + // return Err(e); + // } + + // let ptr = windows::Win32::System::Memory::MapViewOfFile( + // random_mapping, + // windows::Win32::System::Memory::FILE_MAP_ALL_ACCESS, + // 0, + // 0, + // r_addr as usize, + // ); + + // if let Err(e) = last_error() { + // return Err(e); + // } + + // if let Some(ref cfg) = config { + // let actual_distance = (ptr as *const _ as usize).abs_diff(cfg.last_address); + // if actual_distance < cfg.min_distance { + // warn!( + // "New allocation distance to previous allocation is below threshold: {}", + // actual_distance + // ); + + // // remove previous file mapping + // if !windows::Win32::System::Memory::UnmapViewOfFile(ptr).as_bool() { + // if let Err(e) = last_error() { + // return Err(e); + // } + // } + + // continue; + // } + // } + // } // actual memory mapping { @@ -317,6 +376,12 @@ where } } } + + #[cfg(target_os = "windows")] + unsafe fn dealloc(ptr: NonNull) -> Result<(), Self::Error> { + // TODO + Ok(()) + } } // ----------------------------------------------------------------------------- @@ -331,7 +396,7 @@ where /// use runtime::memories::frag::{Frag, FragStrategy}; /// /// // allocates the object at a random address -/// let object = Frag::alloc::(FragStrategy::Direct).unwrap(); +/// let object = Frag::::alloc(FragStrategy::Direct).unwrap(); /// ``` #[derive(Default, Clone)] struct DirectAlloc; @@ -343,7 +408,7 @@ where type Error = MemoryError; #[cfg(any(target_os = "linux", target_os = "macos"))] - fn alloc(config: Option) -> Result, Self::Error> { + fn alloc(config: Option) -> Result, Self::Error> { use random::{thread_rng, Rng}; let actual_size = std::mem::size_of::(); @@ -355,7 +420,6 @@ where // pick a default, if system api call is not successful let default_page_size = 0x1000i64; - #[cfg(any(target_os = "unix", target_os = "linux"))] let _pagesize = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE) .unwrap_or(Some(default_page_size)) .unwrap() as usize; @@ -365,32 +429,32 @@ where // actually leaks memory. loop { unsafe { + let alloc_size = rng.gen::().min(min).max(max); let mem_ptr = { - let alloc_size = rng.gen::().min(min).max(max); - // allocate some randomly sized chunk of memory - let ptr = libc::malloc(alloc_size); - if ptr.is_null() { + let c_ptr = libc::malloc(alloc_size); + if c_ptr.is_null() { continue; } #[cfg(target_os = "macos")] { // on linux it isn't required to commit memory - let error = libc::madvise(ptr, actual_size, libc::MADV_WILLNEED); + let error = libc::madvise(c_ptr, actual_size, libc::MADV_WILLNEED); if error != 0 { error!("memory advise returned an error {}", error); continue; } } - ptr + c_ptr }; if let Some(ref cfg) = config { let actual_distance = (mem_ptr as usize).abs_diff(cfg.last_address); if actual_distance < cfg.min_distance { warn!("New allocation distance to previous allocation is below threshold."); + libc::free(mem_ptr); continue; } } @@ -400,11 +464,24 @@ where let actual_mem = ((mem_ptr as usize) + offset) as *mut T; actual_mem.write(T::default()); - return Ok(NonNull::new_unchecked(actual_mem)); + return Ok(Frag { + ptr: NonNull::new_unchecked(actual_mem), + strategy: FragStrategy::Direct, + info: (mem_ptr, alloc_size), + }); } } } + #[cfg(any(target_os = "linux", target_os = "macos"))] + fn dealloc(frag: Frag) -> Result<(), Self::Error> { + // double free cannot happen due to rust ownership typesystem + unsafe { + libc::free(frag.info.0 as *mut libc::c_void); + } + Ok(()) + } + #[cfg(target_os = "windows")] fn alloc(config: Option) -> Result, Self::Error> { use windows::Win32::System::Memory::{VirtualAlloc, MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE}; @@ -440,6 +517,25 @@ where }; } } + + #[cfg(target_os = "windows")] + unsafe fn dealloc(ptr: NonNull) -> Result<(), Self::Error> { + use windows::Win32::System::Memory::VirtualFree; + + // VirtualFree returns 0/FALSE if the function fails + let res = VirtualFree( + NonNull::as_ptr(ptr) as *mut libc::c_void, + 0, + windows::Win32::System::Memory::MEM_RELEASE, + ) + .as_bool(); + if !res { + if let Err(e) = last_error() { + return Err(e); + } + } + Ok(()) + } } // ----------------------------------------------------------------------------- diff --git a/engine/runtime/tests/alloc_tests.rs b/engine/runtime/tests/alloc_tests.rs index 6226ac0e0..31aa0e14c 100644 --- a/engine/runtime/tests/alloc_tests.rs +++ b/engine/runtime/tests/alloc_tests.rs @@ -7,7 +7,7 @@ use runtime::{ memories::frag::{Frag, FragStrategy}, MemoryError, }; -use std::{fmt::Debug, ptr::NonNull}; +use std::fmt::Debug; #[derive(PartialEq, Debug, Clone)] struct TestStruct { @@ -55,22 +55,23 @@ fn test_allocate_map() { fn test_allocate(allocator: F) -> Result<(), MemoryError> where T: Default + Debug + PartialEq, - F: Fn() -> Result<(NonNull, NonNull), MemoryError>, + F: Fn() -> Result<(Frag, Frag), MemoryError>, { let min_distance = 0xFFFF; let result = allocator(); - assert!(result.is_ok(), "Failed to allocate memory: {:?}", result); + assert!(result.is_ok(), "Failed to allocate memory"); let (a, b) = result.unwrap(); - unsafe { - let aa = a.as_ref(); - let bb = b.as_ref(); + let aa = &*a; + let bb = &*b; - assert!(distance(aa, bb) >= min_distance); - assert_eq!(aa, &T::default()); - assert_eq!(bb, &T::default()); - } + assert!(distance(aa, bb) >= min_distance); + assert_eq!(aa, &T::default()); + assert_eq!(bb, &T::default()); + + assert!(Frag::::dealloc(a).is_ok()); + assert!(Frag::::dealloc(b).is_ok()); Ok(()) } From 02b145876b0429f65e53299df6970fecdc84b4c6 Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Thu, 16 Jun 2022 23:16:07 +0200 Subject: [PATCH 31/46] Add deallocation for windows --- engine/runtime/src/memories/frag.rs | 120 ++++++++++++++++++---------- 1 file changed, 79 insertions(+), 41 deletions(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index da617e6f7..dcd4fa4fb 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -56,7 +56,12 @@ pub trait Alloc { pub struct Frag { ptr: NonNull, strategy: FragStrategy, + + #[cfg(any(target_os = "linux", target_os = "macos"))] info: (*mut libc::c_void, usize), + + #[cfg(target_os = "windows")] + info: Option, } impl Deref for Frag { @@ -228,12 +233,7 @@ where if actual_distance < cfg.min_distance { warn!("New allocation distance to previous allocation is below threshold."); - // Deallocate used memory - let res = libc::munmap(c_ptr, desired_alloc_size); - if res != 0 { - error!("Failed to munmap"); - return Err(MemoryError::Allocation("Failed to munmap".to_owned())); - } + dealloc_map(c_ptr, desired_alloc_size)?; continue; } } @@ -271,21 +271,13 @@ where #[cfg(any(target_os = "linux", target_os = "macos"))] fn dealloc(frag: Frag) -> Result<(), Self::Error> { - // munmap returns 0 on success - unsafe { - let res = libc::munmap(frag.info.0, frag.info.1 as libc::size_t); - if res != 0 { - let os_error = std::io::Error::last_os_error(); - return Err(MemoryError::Allocation(format!("Failed to munmap: {}", os_error))); - } - } - Ok(()) + dealloc_map(frag.info.0, frag.info.1 as libc::size_t) } #[cfg(target_os = "windows")] - fn alloc(config: Option) -> Result, Self::Error> { - use random::{thread_rng, Rng}; - let mut rng = thread_rng(); + fn alloc(_config: Option) -> Result, Self::Error> { + // use random::thread_rng; + // let mut rng = thread_rng(); let handle = windows::Win32::Foundation::INVALID_HANDLE_VALUE; loop { @@ -371,16 +363,23 @@ where actual_mem.write(T::default()); - return Ok(NonNull::new_unchecked(actual_mem as *mut T)); + return Ok(Frag { + ptr: NonNull::new_unchecked(actual_mem as *mut T), + strategy: FragStrategy::Map, + info: Some(actual_mapping), + }); } } } } #[cfg(target_os = "windows")] - unsafe fn dealloc(ptr: NonNull) -> Result<(), Self::Error> { - // TODO - Ok(()) + fn dealloc(frag: Frag) -> Result<(), Self::Error> { + if let Some(handle) = frag.info { + dealloc_map(handle) + } else { + Err(MemoryError::Allocation("Cannot release file handle".to_owned())) + } } } @@ -410,9 +409,9 @@ where #[cfg(any(target_os = "linux", target_os = "macos"))] fn alloc(config: Option) -> Result, Self::Error> { use random::{thread_rng, Rng}; + let mut rng = thread_rng(); let actual_size = std::mem::size_of::(); - let mut rng = thread_rng(); let min = 0xFFFF; let max = 0xFFFF_FFFF; @@ -454,7 +453,7 @@ where let actual_distance = (mem_ptr as usize).abs_diff(cfg.last_address); if actual_distance < cfg.min_distance { warn!("New allocation distance to previous allocation is below threshold."); - libc::free(mem_ptr); + dealloc_direct(mem_ptr)?; continue; } } @@ -475,15 +474,11 @@ where #[cfg(any(target_os = "linux", target_os = "macos"))] fn dealloc(frag: Frag) -> Result<(), Self::Error> { - // double free cannot happen due to rust ownership typesystem - unsafe { - libc::free(frag.info.0 as *mut libc::c_void); - } - Ok(()) + dealloc_direct(frag.info.0 as *mut libc::c_void) } #[cfg(target_os = "windows")] - fn alloc(config: Option) -> Result, Self::Error> { + fn alloc(config: Option) -> Result, Self::Error> { use windows::Win32::System::Memory::{VirtualAlloc, MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE}; loop { @@ -507,35 +502,78 @@ where let actual_distance = (actual_mem as usize).abs_diff(cfg.last_address); if actual_distance < cfg.min_distance { warn!("New allocation distance to previous allocation is below threshold."); + dealloc_direct(actual_mem)?; continue; } } let actual_mem = actual_mem as *mut T; actual_mem.write(T::default()); - return Ok(NonNull::new_unchecked(actual_mem)); - }; + return Ok(Frag { + ptr: NonNull::new_unchecked(actual_mem), + strategy: FragStrategy::Direct, + info: None, + }); + } } } #[cfg(target_os = "windows")] - unsafe fn dealloc(ptr: NonNull) -> Result<(), Self::Error> { - use windows::Win32::System::Memory::VirtualFree; + fn dealloc(frag: Frag) -> Result<(), Self::Error> { + dealloc_direct(NonNull::as_ptr(frag.ptr) as *mut libc::c_void) + } +} + +#[cfg(any(target_os = "linux", target_os = "macos"))] +fn dealloc_map(ptr: *mut libc::c_void, size: libc::size_t) -> Result<(), MemoryError> { + // munmap returns 0 on success + unsafe { + let res = libc::munmap(ptr, size); + if res != 0 { + let os_error = std::io::Error::last_os_error(); + return Err(MemoryError::Allocation(format!("Failed to munmap: {}", os_error))); + } + } + Ok(()) +} + +#[cfg(target_os = "windows")] +fn dealloc_map(handle: windows::Win32::Foundation::HANDLE) -> Result<(), MemoryError> { + // CloseHandle returns 0/FALSE when failing + unsafe { + let res = windows::Win32::Foundation::CloseHandle(handle); + if !res.as_bool() { + if let Err(e) = last_error() { + return Err(e); + } + } + } + Ok(()) +} + +#[cfg(any(target_os = "linux", target_os = "macos"))] +fn dealloc_direct(ptr: *mut libc::c_void) -> Result<(), MemoryError> { + // double free cannot happen due to rust ownership typesystem + unsafe { + libc::free(ptr); + } + Ok(()) +} +#[cfg(target_os = "windows")] +fn dealloc_direct(ptr: *mut libc::c_void) -> Result<(), MemoryError> { + use windows::Win32::System::Memory::VirtualFree; + + unsafe { // VirtualFree returns 0/FALSE if the function fails - let res = VirtualFree( - NonNull::as_ptr(ptr) as *mut libc::c_void, - 0, - windows::Win32::System::Memory::MEM_RELEASE, - ) - .as_bool(); + let res = VirtualFree(ptr, 0, windows::Win32::System::Memory::MEM_RELEASE).as_bool(); if !res { if let Err(e) = last_error() { return Err(e); } } - Ok(()) } + Ok(()) } // ----------------------------------------------------------------------------- From db8f272742aef85c860dd9d6aefd30fcb4fc10d6 Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Fri, 17 Jun 2022 00:37:54 +0200 Subject: [PATCH 32/46] Fix memory leak from windows map strategy --- engine/runtime/src/memories/frag.rs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index dcd4fa4fb..a6f30b2e9 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -61,7 +61,7 @@ pub struct Frag { info: (*mut libc::c_void, usize), #[cfg(target_os = "windows")] - info: Option, + info: Option, } impl Deref for Frag { @@ -349,13 +349,14 @@ where return Err(e); } - let actual_mem = windows::Win32::System::Memory::MapViewOfFile( + let mem_view = windows::Win32::System::Memory::MapViewOfFile( actual_mapping, windows::Win32::System::Memory::FILE_MAP_ALL_ACCESS, 0, 0, actual_size as usize, - ) as *mut T; + ); + let actual_mem = mem_view as *mut T; if let Err(e) = last_error() { return Err(e); @@ -364,9 +365,9 @@ where actual_mem.write(T::default()); return Ok(Frag { - ptr: NonNull::new_unchecked(actual_mem as *mut T), + ptr: NonNull::new_unchecked(actual_mem), strategy: FragStrategy::Map, - info: Some(actual_mapping), + info: Some((actual_mapping, mem_view)), }); } } @@ -375,8 +376,8 @@ where #[cfg(target_os = "windows")] fn dealloc(frag: Frag) -> Result<(), Self::Error> { - if let Some(handle) = frag.info { - dealloc_map(handle) + if let Some((handle, view)) = frag.info { + dealloc_map(handle, view) } else { Err(MemoryError::Allocation("Cannot release file handle".to_owned())) } @@ -538,9 +539,17 @@ fn dealloc_map(ptr: *mut libc::c_void, size: libc::size_t) -> Result<(), MemoryE } #[cfg(target_os = "windows")] -fn dealloc_map(handle: windows::Win32::Foundation::HANDLE) -> Result<(), MemoryError> { - // CloseHandle returns 0/FALSE when failing +fn dealloc_map(handle: windows::Win32::Foundation::HANDLE, view: *const libc::c_void) -> Result<(), MemoryError> { unsafe { + // UnmapViewOfFile returns 0/FALSE when failing + let res = windows::Win32::System::Memory::UnmapViewOfFile(view); + if !res.as_bool() { + if let Err(e) = last_error() { + return Err(e); + } + } + + // CloseHandle returns 0/FALSE when failing let res = windows::Win32::Foundation::CloseHandle(handle); if !res.as_bool() { if let Err(e) = last_error() { From fff39eecb592ca6c391b7099a39c4c8dc8262666 Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Fri, 17 Jun 2022 00:42:04 +0200 Subject: [PATCH 33/46] Fix typo in windows code --- engine/runtime/src/memories/frag.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index a6f30b2e9..c2586da24 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -61,7 +61,7 @@ pub struct Frag { info: (*mut libc::c_void, usize), #[cfg(target_os = "windows")] - info: Option, + info: Option<(windows::Win32::Foundation::HANDLE, *const libc::c_void)>, } impl Deref for Frag { From aafe93006c7b8f00be6827cacf4fd25fefb9d3b9 Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Tue, 5 Jul 2022 16:23:37 +0200 Subject: [PATCH 34/46] Add hybrid strategy in fragments --- engine/benches/engine_bench.rs | 68 +++++++---------- engine/runtime/Cargo.toml | 3 +- engine/runtime/src/memories/frag.rs | 95 +++++++---------------- engine/runtime/tests/alloc_tests.rs | 26 ++++--- engine/runtime/tests/memory_leak_test.rs | 96 ++++++++++++++++++++++++ 5 files changed, 167 insertions(+), 121 deletions(-) create mode 100644 engine/runtime/tests/memory_leak_test.rs diff --git a/engine/benches/engine_bench.rs b/engine/benches/engine_bench.rs index 02c79a6d3..8dc76e5f2 100644 --- a/engine/benches/engine_bench.rs +++ b/engine/benches/engine_bench.rs @@ -108,54 +108,43 @@ fn bench_store_decompress(c: &mut Criterion) { }); } -fn bench_allocate_direct(c: &mut Criterion) { - #[allow(dead_code)] - struct TestStruct { - id: usize, - name: String, - } +#[allow(dead_code)] +struct TestStruct { + id: usize, + name: String, +} - impl Default for TestStruct { - fn default() -> Self { - Self { - id: 0xFFFF_FFFF_FFFF_FFFF, - name: "Some TestingStruct".to_owned(), - } +impl Default for TestStruct { + fn default() -> Self { + Self { + id: 0xFFFF_FFFF_FFFF_FFFF, + name: "Some TestingStruct".to_owned(), } } +} - c.bench_function("Allocate memory direct", |b| { - b.iter(|| { - let strat = FragStrategy::Direct; - if let Ok((m1, m2)) = Frag::::alloc(strat) { - Frag::dealloc(m1).expect("error while deallocating"); - Frag::dealloc(m2).expect("error while deallocating"); - } else { - panic!("error while allocating memory"); - } - }); - }); +fn bench_allocate_direct(c: &mut Criterion) { + bench_allocate_strat(c, FragStrategy::Direct); } fn bench_allocate_mapped(c: &mut Criterion) { - #[allow(dead_code)] - struct TestStruct { - id: usize, - name: String, - } + bench_allocate_strat(c, FragStrategy::Map); +} - impl Default for TestStruct { - fn default() -> Self { - Self { - id: 0xFFFF_FFFF_FFFF_FFFF, - name: "Some TestingStruct".to_owned(), - } - } - } +fn bench_allocate_hybrid(c: &mut Criterion) { + bench_allocate_strat(c, FragStrategy::Hybrid); +} + +fn bench_allocate_strat(c: &mut Criterion, strat: FragStrategy) { + let bench_name = match strat { + FragStrategy::Direct => "Direct allocations", + FragStrategy::Map => "Map allocations", + FragStrategy::Hybrid => "Hybrid allocations", + _ => unreachable!(), + }; - c.bench_function("Allocate memory mapped", |b| { + c.bench_function(bench_name, |b| { b.iter(|| { - let strat = FragStrategy::Map; if let Ok((m1, m2)) = Frag::::alloc(strat) { Frag::dealloc(m1).expect("error while deallocating"); Frag::dealloc(m2).expect("error while deallocating"); @@ -176,6 +165,7 @@ criterion_group!( bench_store_decompress, bench_vault_write, bench_allocate_direct, - bench_allocate_mapped + bench_allocate_mapped, + bench_allocate_hybrid ); criterion_main!(benches); diff --git a/engine/runtime/Cargo.toml b/engine/runtime/Cargo.toml index e63f71abd..4bcdca9c1 100644 --- a/engine/runtime/Cargo.toml +++ b/engine/runtime/Cargo.toml @@ -26,4 +26,5 @@ nix = { version = "0.24.1" } [dev-dependencies] serde_json = { version = "1.0" } -env_logger = { version = "0.9" } \ No newline at end of file +env_logger = { version = "0.9" } +dhat = { version = "0.3" } \ No newline at end of file diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index c2586da24..635355acd 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -25,20 +25,22 @@ use std::{ }; /// Fragmenting strategy to allocate memory at random addresses. -#[derive(Debug, Clone, Copy)] -#[non_exhaustive] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum FragStrategy { /// Anonymously maps a region of memory Map, /// Using system allocator (`malloc` on linux/bsd/macos and `VirtualAlloc` on windows) Direct, + + /// Allocate using both strategy + Hybrid, } // ----------------------------------------------------------------------------- /// Custom allocator trait -pub trait Alloc { +pub trait Alloc { type Error; /// Allocates `T`, returns an error if something wrong happened. Takes an @@ -53,7 +55,8 @@ pub trait Alloc { /// Frag is being used as control object to load different allocators /// according to their strategy -pub struct Frag { +#[derive(Clone)] +pub struct Frag { ptr: NonNull, strategy: FragStrategy, @@ -64,7 +67,7 @@ pub struct Frag { info: Option<(windows::Win32::Foundation::HANDLE, *const libc::c_void)>, } -impl Deref for Frag { +impl Deref for Frag { type Target = T; fn deref(&self) -> &Self::Target { @@ -72,7 +75,7 @@ impl Deref for Frag { } } -impl DerefMut for Frag { +impl DerefMut for Frag { fn deref_mut(&mut self) -> &mut Self::Target { unsafe { self.ptr.as_mut() } } @@ -99,7 +102,7 @@ impl FragConfig { } } -impl Frag { +impl Frag { /// Returns a fragmenting allocator by strategy /// /// # Example @@ -110,17 +113,20 @@ impl Frag { /// let object = Frag::by_strategy(FragStrategy::Default).unwrap(); /// ``` - pub fn alloc_single(strategy: FragStrategy, config: Option) -> Result, MemoryError> { - match strategy { - FragStrategy::Direct => DirectAlloc::alloc(config), - FragStrategy::Map => MemoryMapAlloc::alloc(config), - } - } - /// Tries to allocate two objects of the same type with a minimum distance in memory space. pub fn alloc2(strategy: FragStrategy, distance: usize) -> Result<(Frag, Frag), MemoryError> { - let a = Self::alloc_single(strategy, None)?; - let b = Self::alloc_single(strategy, Some(FragConfig::new(a.ptr.as_ptr() as usize, distance)))?; + let a = match strategy { + FragStrategy::Direct => DirectAlloc::alloc(None), + FragStrategy::Map => MemoryMapAlloc::alloc(None), + FragStrategy::Hybrid => DirectAlloc::alloc(None), + }?; + + let config = Some(FragConfig::new(a.ptr.as_ptr() as usize, distance)); + let b = match strategy { + FragStrategy::Direct => DirectAlloc::alloc(config), + FragStrategy::Map => MemoryMapAlloc::alloc(config), + FragStrategy::Hybrid => MemoryMapAlloc::alloc(config), + }?; let actual_distance = calc_distance(&*a, &*b); if actual_distance < distance { @@ -153,6 +159,7 @@ impl Frag { match ptr.strategy { FragStrategy::Direct => DirectAlloc::dealloc(ptr), FragStrategy::Map => MemoryMapAlloc::dealloc(ptr), + FragStrategy::Hybrid => unreachable!("Frag are allocated only using map or direct allocation"), } } } @@ -176,7 +183,7 @@ struct MemoryMapAlloc; impl Alloc for MemoryMapAlloc where - T: Default, + T: Default + Clone, { type Error = MemoryError; @@ -228,11 +235,11 @@ where continue; } + // Check that new memory is far enough if let Some(ref cfg) = config { let actual_distance = (c_ptr as usize).abs_diff(cfg.last_address); if actual_distance < cfg.min_distance { warn!("New allocation distance to previous allocation is below threshold."); - dealloc_map(c_ptr, desired_alloc_size)?; continue; } @@ -282,56 +289,6 @@ where let handle = windows::Win32::Foundation::INVALID_HANDLE_VALUE; loop { unsafe { - // allocation prelude - // { - // let r_addr = rng.gen::() >> 4; - - // let random_mapping = windows::Win32::System::Memory::CreateFileMappingW( - // handle, - // std::ptr::null_mut(), - // windows::Win32::System::Memory::PAGE_READWRITE, - // 0, - // r_addr, - // windows::core::PCWSTR(std::ptr::null_mut()), - // ) - // .map_err(|e| MemoryError::Allocation(e.to_string()))?; - - // if let Err(e) = last_error() { - // return Err(e); - // } - - // let ptr = windows::Win32::System::Memory::MapViewOfFile( - // random_mapping, - // windows::Win32::System::Memory::FILE_MAP_ALL_ACCESS, - // 0, - // 0, - // r_addr as usize, - // ); - - // if let Err(e) = last_error() { - // return Err(e); - // } - - // if let Some(ref cfg) = config { - // let actual_distance = (ptr as *const _ as usize).abs_diff(cfg.last_address); - // if actual_distance < cfg.min_distance { - // warn!( - // "New allocation distance to previous allocation is below threshold: {}", - // actual_distance - // ); - - // // remove previous file mapping - // if !windows::Win32::System::Memory::UnmapViewOfFile(ptr).as_bool() { - // if let Err(e) = last_error() { - // return Err(e); - // } - // } - - // continue; - // } - // } - // } - // actual memory mapping { let actual_size = std::mem::size_of::() as u32; @@ -403,7 +360,7 @@ struct DirectAlloc; impl Alloc for DirectAlloc where - T: Default, + T: Default + Clone, { type Error = MemoryError; diff --git a/engine/runtime/tests/alloc_tests.rs b/engine/runtime/tests/alloc_tests.rs index 31aa0e14c..7ce99f5a2 100644 --- a/engine/runtime/tests/alloc_tests.rs +++ b/engine/runtime/tests/alloc_tests.rs @@ -26,32 +26,33 @@ impl Default for TestStruct { #[test] fn test_allocate_direct() { - let _ = env_logger::builder() - .is_test(true) - .filter(None, log::LevelFilter::Info) - .try_init(); - - info!("Test Fixed Distance"); - assert!(test_allocate::(|| Frag::alloc(FragStrategy::Direct)).is_ok()); - - info!("Test Arbitrary Distance"); - assert!(test_allocate::(|| Frag::alloc2(FragStrategy::Direct, 0xFFFF)).is_ok()); + test_allocate_strategy(FragStrategy::Direct) } #[test] fn test_allocate_map() { + test_allocate_strategy(FragStrategy::Map) +} + +#[test] +fn test_allocate_hybrid() { + test_allocate_strategy(FragStrategy::Hybrid) +} + +fn test_allocate_strategy(strat: FragStrategy) { let _ = env_logger::builder() .is_test(true) .filter(None, log::LevelFilter::Info) .try_init(); info!("Test Fixed Distance"); - assert!(test_allocate::(|| Frag::alloc(FragStrategy::Map)).is_ok()); + assert!(test_allocate::(|| Frag::alloc(strat)).is_ok()); info!("Test Arbitrary Distance"); - assert!(test_allocate::(|| Frag::alloc2(FragStrategy::Map, 0xFFFF)).is_ok()); + assert!(test_allocate::(|| Frag::alloc2(strat, 0xFFFF)).is_ok()); } + fn test_allocate(allocator: F) -> Result<(), MemoryError> where T: Default + Debug + PartialEq, @@ -76,6 +77,7 @@ where Ok(()) } + // ---------------------------------------------------------------------------- /// Calculates the distance between two pointers diff --git a/engine/runtime/tests/memory_leak_test.rs b/engine/runtime/tests/memory_leak_test.rs new file mode 100644 index 000000000..ebb6b4906 --- /dev/null +++ b/engine/runtime/tests/memory_leak_test.rs @@ -0,0 +1,96 @@ +use runtime::{ + memories::frag::{Frag, FragStrategy}, +}; + +use std::fmt::Debug; + +#[global_allocator] +static ALLOC: dhat::Alloc = dhat::Alloc; + + +#[derive(PartialEq, Debug, Clone)] +struct TestStruct { + id: usize, + name: String, +} + +impl Default for TestStruct { + fn default() -> Self { + Self { + id: 123456789, + name: "Some heap allocated value".to_owned(), + } + } +} + +const NB_ALLOC: usize = 20; +// We run this the test binary with valgrind to check for potential leak +#[test] +fn test_memory_leak_without_dealloc() { + let _ = alloc_frags(FragStrategy::Direct, NB_ALLOC); +} + +#[test] +fn test_memory_no_leak() { + let frags = alloc_frags(FragStrategy::Direct, NB_ALLOC); + dealloc_frags(frags); +} + + +// Test to check the dhat tool +// TODO: Not working yet +fn test_dhat() { + + let _profiler = dhat::Profiler::builder().testing().build(); + + let stats = dhat::HeapStats::get(); + dhat::assert_eq!(stats.curr_blocks, 0); + println!("0 - Blocks allocated: {}, bytes allocated: {}", stats.curr_blocks, stats.curr_bytes); + + const SIZE: usize = 1000; + let b = [5i32; SIZE]; + + let mut vec = Vec::with_capacity(10); + println!("0.5 - Blocks allocated: {}, bytes allocated: {}", stats.curr_blocks, stats.curr_bytes); + unsafe { + for i in 0..10 { + let ptr = libc::malloc(SIZE * 4) as *mut i32; + std::ptr::copy(&b as *const i32, ptr, SIZE); + let _ = std::ptr::read(ptr); + vec.push(ptr); + + let stats = dhat::HeapStats::get(); + println!("{} - Blocks allocated: {}, bytes allocated: {}", i, stats.curr_blocks, stats.curr_bytes); + + } + + let stats = dhat::HeapStats::get(); + dhat::assert_eq!(stats.curr_blocks, 1); + println!("final - Blocks allocated: {}, bytes allocated: {}", stats.curr_blocks, stats.curr_bytes); + dhat::assert_eq!(stats.curr_blocks, 0); + + // libc::free(ptr); + // let stats = dhat::HeapStats::get(); + // dhat::assert_eq!(stats.curr_blocks, 0); + } +} + + +// Goal +fn alloc_frags(strat: FragStrategy, nb_alloc: usize) -> Vec> { + let mut v = vec![]; + for _ in 0..nb_alloc { + let frags = Frag::::alloc(strat); + assert!(frags.is_ok()); + let (f1, f2) = frags.unwrap(); + v.push(f1); + v.push(f2); + } + v +} + +fn dealloc_frags(vec: Vec>) { + for frag in vec.into_iter() { + assert!(Frag::dealloc(frag).is_ok()); + } +} From ad80006c6fd4bca835afb27f456151d75970feda Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Wed, 6 Jul 2022 02:47:49 +0200 Subject: [PATCH 35/46] Integrate memory fragments into non-contiguous memory --- engine/runtime/src/lib.rs | 3 + engine/runtime/src/memories/frag.rs | 90 +++-- .../src/memories/noncontiguous_memory.rs | 367 ++++++++---------- .../tests/{alloc_tests.rs => frags_tests.rs} | 56 ++- engine/runtime/tests/memory_leak_test.rs | 12 +- 5 files changed, 266 insertions(+), 262 deletions(-) rename engine/runtime/tests/{alloc_tests.rs => frags_tests.rs} (53%) diff --git a/engine/runtime/src/lib.rs b/engine/runtime/src/lib.rs index d2885c707..eee87d493 100644 --- a/engine/runtime/src/lib.rs +++ b/engine/runtime/src/lib.rs @@ -45,6 +45,9 @@ pub enum MemoryError { #[error("Intended operation failed: ({0})")] Operation(String), + + #[error("Illegal tentative of using zeroized memory")] + IllegalZeroizedUsage, } /// A simple trait to force the types to call `zeroize()` when dropping diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 635355acd..281c1c5fd 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -20,9 +20,13 @@ use crate::MemoryError; use log::*; use std::{ fmt::Debug, - ops::{Deref, DerefMut}, ptr::NonNull, }; +use zeroize::Zeroize; + +// The minimum distance we consider between 2 fragments +// This is the page size for most linux systems +pub const FRAG_MIN_DISTANCE: usize = 0x1000; /// Fragmenting strategy to allocate memory at random addresses. #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -48,7 +52,7 @@ pub trait Alloc { fn alloc(config: Option) -> Result, Self::Error>; /// Deallocate `T`, returns an error if something wrong happened. - fn dealloc(frag: Frag) -> Result<(), Self::Error>; + fn dealloc(frag: &mut Frag) -> Result<(), Self::Error>; } // ----------------------------------------------------------------------------- @@ -60,6 +64,9 @@ pub struct Frag { ptr: NonNull, strategy: FragStrategy, + // If the fragment is still valid/alive + live: bool, + #[cfg(any(target_os = "linux", target_os = "macos"))] info: (*mut libc::c_void, usize), @@ -67,17 +74,20 @@ pub struct Frag { info: Option<(windows::Win32::Foundation::HANDLE, *const libc::c_void)>, } -impl Deref for Frag { - type Target = T; - - fn deref(&self) -> &Self::Target { - unsafe { self.ptr.as_ref() } +impl Zeroize for Frag { + fn zeroize(&mut self) { + self.live = false; + unsafe { + let ptr: *mut T = self.ptr.as_mut(); + *ptr = T::default(); + } } } -impl DerefMut for Frag { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { self.ptr.as_mut() } +impl Drop for Frag { + fn drop(&mut self) { + self.zeroize(); + Frag::dealloc(self).expect("Error while deallocating fragment memory"); } } @@ -128,7 +138,7 @@ impl Frag { FragStrategy::Hybrid => MemoryMapAlloc::alloc(config), }?; - let actual_distance = calc_distance(&*a, &*b); + let actual_distance = calc_distance(a.get()?, b.get()?); if actual_distance < distance { error!( "Distance between parts below threshold: \nthreshold: 0x{:016X} \nactual_distance: 0x{:016X}", @@ -150,18 +160,45 @@ impl Frag { Ok((a, b)) } - /// Tries to allocate two objects of the same type with a default minimum distance in memory space of `0xFFFF`. - pub fn alloc(strategy: FragStrategy) -> Result<(Frag, Frag), MemoryError> { - Self::alloc2(strategy, 0xFFFF) + /// Tries to allocate two objects of the same type with a default minimum distance in memory space of `FRAG_MIN_DISTANCE`. + pub fn alloc(strategy: FragStrategy, data1: T, data2: T) -> Result<(Frag, Frag), MemoryError> { + let (mut f1, mut f2) = Self::alloc2(strategy, 100 * FRAG_MIN_DISTANCE)?; + f1.set(data1)?; + f2.set(data2)?; + Ok((f1, f2)) } - pub fn dealloc(ptr: Frag) -> Result<(), MemoryError> { + pub fn dealloc(ptr: &mut Frag) -> Result<(), MemoryError> { match ptr.strategy { FragStrategy::Direct => DirectAlloc::dealloc(ptr), FragStrategy::Map => MemoryMapAlloc::dealloc(ptr), FragStrategy::Hybrid => unreachable!("Frag are allocated only using map or direct allocation"), } } + + pub fn is_live(&self) -> bool { + self.live + } + + pub fn get(&self) -> Result<&T, MemoryError>{ + if !self.live { + return Err(MemoryError::IllegalZeroizedUsage) + } + unsafe { + Ok(self.ptr.as_ref()) + } + } + + pub fn set(&mut self, data: T) -> Result<(), MemoryError>{ + if !self.live { + return Err(MemoryError::IllegalZeroizedUsage) + } + unsafe { + let ptr: *mut T = self.ptr.as_mut(); + *ptr = data; + } + Ok(()) + } } // ----------------------------------------------------------------------------- @@ -170,13 +207,6 @@ impl Frag { /// of this memory will be randomly seeded. /// /// The actual implementation is system dependent and might vary. -/// -/// # Example -/// ``` -/// use runtime::memories::frag::{Frag, FragStrategy}; -/// -/// // allocates the object at a random address -/// let object = Frag::::alloc(FragStrategy::Map).unwrap(); /// ``` #[derive(Default, Clone)] struct MemoryMapAlloc; @@ -269,6 +299,7 @@ where return Ok(Frag { ptr: NonNull::new_unchecked(ptr), strategy: FragStrategy::Map, + live: true, info: (c_ptr, desired_alloc_size), }); } @@ -277,7 +308,7 @@ where } #[cfg(any(target_os = "linux", target_os = "macos"))] - fn dealloc(frag: Frag) -> Result<(), Self::Error> { + fn dealloc(frag: &mut Frag) -> Result<(), Self::Error> { dealloc_map(frag.info.0, frag.info.1 as libc::size_t) } @@ -324,6 +355,7 @@ where return Ok(Frag { ptr: NonNull::new_unchecked(actual_mem), strategy: FragStrategy::Map, + live: true, info: Some((actual_mapping, mem_view)), }); } @@ -347,14 +379,6 @@ where /// resizing the chunk to the desired size of the to be allocated object. /// /// The actual implementation is system dependent and might vary. -/// -/// # Example -/// ``` -/// use runtime::memories::frag::{Frag, FragStrategy}; -/// -/// // allocates the object at a random address -/// let object = Frag::::alloc(FragStrategy::Direct).unwrap(); -/// ``` #[derive(Default, Clone)] struct DirectAlloc; @@ -424,6 +448,7 @@ where return Ok(Frag { ptr: NonNull::new_unchecked(actual_mem), strategy: FragStrategy::Direct, + live: true, info: (mem_ptr, alloc_size), }); } @@ -431,7 +456,7 @@ where } #[cfg(any(target_os = "linux", target_os = "macos"))] - fn dealloc(frag: Frag) -> Result<(), Self::Error> { + fn dealloc(frag: &mut Frag) -> Result<(), Self::Error> { dealloc_direct(frag.info.0 as *mut libc::c_void) } @@ -470,6 +495,7 @@ where return Ok(Frag { ptr: NonNull::new_unchecked(actual_mem), strategy: FragStrategy::Direct, + live: true, info: None, }); } diff --git a/engine/runtime/src/memories/noncontiguous_memory.rs b/engine/runtime/src/memories/noncontiguous_memory.rs index 03c45d5c1..7809c6865 100644 --- a/engine/runtime/src/memories/noncontiguous_memory.rs +++ b/engine/runtime/src/memories/noncontiguous_memory.rs @@ -6,7 +6,12 @@ use crate::{ locked_memory::LockedMemory, - memories::{buffer::Buffer, file_memory::FileMemory, ram_memory::RamMemory}, + memories::{ + buffer::Buffer, + file_memory::FileMemory, + frag::{Frag, FragStrategy}, + ram_memory::RamMemory, + }, utils::*, MemoryError::*, *, @@ -27,20 +32,22 @@ use serde::{ use std::{cell::RefCell, sync::Mutex}; +#[allow(dead_code)] static IMPOSSIBLE_CASE: &str = "NonContiguousMemory: this case should not happen if allocated properly"; static POISONED_LOCK: &str = "NonContiguousMemory potentially in an unsafe state"; // Currently we only support data of 32 bytes in noncontiguous memory pub const NC_DATA_SIZE: usize = 32; -// Temporary, we currently only use non contiguous with the two shards in RAM -pub const NC_CONFIGURATION: NCConfig = FullRam; +// For serialization/deserialization we choose this fullram config +pub const NC_CONFIGURATION_SERIALIZATION: NCConfig = FullRam; #[derive(Debug, PartialEq, Eq, Clone)] pub enum NCConfig { FullFile, FullRam, RamAndFile, + FragAllocation(FragStrategy), } use NCConfig::*; @@ -48,8 +55,9 @@ use NCConfig::*; /// Shards of memory which composes a non contiguous memory #[derive(Clone)] enum MemoryShard { - FileShard(FileMemory), - RamShard(RamMemory), + File(FileMemory), + Ram(RamMemory), + Frag(Frag<[u8; NC_DATA_SIZE]>), } use MemoryShard::*; @@ -82,27 +90,14 @@ impl LockedMemory for NonContiguousMemory { /// Unlocks the memory and returns an unlocked Buffer /// To retrieve secret value you xor the hash contained in shard1 with value in shard2 fn unlock(&self) -> Result, MemoryError> { - let data1 = blake2b::Blake2b256::digest(&self.get_buffer_from_shard1().borrow()); - - let mut2 = self.shard2.lock().expect(POISONED_LOCK); - let data = match &*mut2.borrow() { - RamShard(ram2) => { - let buf = ram2.unlock()?; - let x = xor(&data1, &buf.borrow(), NC_DATA_SIZE); - x - } - FileShard(fm) => { - let buf = fm.unlock()?; - let x = xor(&data1, &buf.borrow(), NC_DATA_SIZE); - x - } - }; - drop(mut2); + let (data1, data2) = self.get_shards_data()?; + let data1 = &blake2b::Blake2b256::digest(&data1); + let reconstructed_data = xor(data1, &data2, NC_DATA_SIZE); // Refresh the shards after each use self.refresh()?; - Ok(Buffer::alloc(&data, NC_DATA_SIZE)) + Ok(Buffer::alloc(&reconstructed_data, NC_DATA_SIZE)) } } @@ -116,24 +111,7 @@ impl NonContiguousMemory { let digest = blake2b::Blake2b256::digest(&random); let digest = xor(&digest, payload, NC_DATA_SIZE); - let ram1 = RamMemory::alloc(&random, NC_DATA_SIZE)?; - - let shard1 = RamShard(ram1); - - let shard2 = match config { - RamAndFile => { - let fmem = FileMemory::alloc(&digest, NC_DATA_SIZE)?; - FileShard(fmem) - } - FullRam => { - let ram2 = RamMemory::alloc(&digest, NC_DATA_SIZE)?; - RamShard(ram2) - } - // Not supported yet TODO - _ => { - return Err(LockNotAvailable); - } - }; + let (shard1, shard2) = MemoryShard::new_shards(&random, &digest, &config)?; let mem = NonContiguousMemory { shard1: Mutex::new(RefCell::new(shard1)), @@ -144,56 +122,38 @@ impl NonContiguousMemory { Ok(mem) } - fn get_buffer_from_shard1(&self) -> Buffer { - let mut1 = self.shard1.lock().expect(POISONED_LOCK); - let shard1 = &*mut1.borrow(); - - match shard1 { - RamShard(ram) => ram.unlock().expect("Failed to retrieve buffer from Ram shard"), - _ => unreachable!("{}", IMPOSSIBLE_CASE), - } - } - // Refresh the shards to increase security, may be called every _n_ seconds or // punctually - fn refresh(&self) -> Result<(), MemoryError> { + pub fn refresh(&self) -> Result<(), MemoryError> { let random = random_vec(NC_DATA_SIZE); + let (old_data1, old_data2) = self.get_shards_data()?; - // Refresh shard1 - let buf_of_old_shard1 = self.get_buffer_from_shard1(); - - let data_of_old_shard1 = &buf_of_old_shard1.borrow(); + let new_data1 = xor(&old_data1, &random, NC_DATA_SIZE); - let new_data1 = xor(data_of_old_shard1, &random, NC_DATA_SIZE); - let new_shard1 = RamShard(RamMemory::alloc(&new_data1, NC_DATA_SIZE)?); + let hash_of_old_shard1 = &blake2b::Blake2b256::digest(&old_data1); + let hash_of_new_shard1 = &blake2b::Blake2b256::digest(&new_data1); - let hash_of_old_shard1 = blake2b::Blake2b256::digest(data_of_old_shard1); - let hash_of_new_shard1 = blake2b::Blake2b256::digest(&new_data1); + let new_data2 = xor(&old_data2, hash_of_old_shard1, NC_DATA_SIZE); + let new_data2 = xor(&new_data2, hash_of_new_shard1, NC_DATA_SIZE); - let mut2 = self.shard2.lock().expect(POISONED_LOCK); - let new_shard2 = match &*mut2.borrow() { - RamShard(ram2) => { - let buf = ram2.unlock()?; - let new_data2 = xor(&buf.borrow(), &hash_of_old_shard1, NC_DATA_SIZE); - let new_data2 = xor(&new_data2, &hash_of_new_shard1, NC_DATA_SIZE); - RamShard(RamMemory::alloc(&new_data2, NC_DATA_SIZE)?) - } - FileShard(fm) => { - let buf = fm.unlock()?; - let new_data2 = xor(&buf.borrow(), &hash_of_old_shard1, NC_DATA_SIZE); - let new_data2 = xor(&new_data2, &hash_of_new_shard1, NC_DATA_SIZE); - let new_fm = FileMemory::alloc(&new_data2, NC_DATA_SIZE)?; - FileShard(new_fm) - } - }; + let (shard1, shard2) = MemoryShard::new_shards(&new_data1, &new_data2, &self.config)?; - let mut1 = self.shard1.lock().expect(POISONED_LOCK); - mut1.replace(new_shard1); - mut2.replace(new_shard2); + let m1 = self.shard1.lock().expect(POISONED_LOCK); + let m2 = self.shard2.lock().expect(POISONED_LOCK); + m1.replace(shard1); + m2.replace(shard2); Ok(()) } + fn get_shards_data(&self) -> Result<(Vec, Vec), MemoryError> { + let m1 = self.shard1.lock().expect(POISONED_LOCK); + let m2 = self.shard2.lock().expect(POISONED_LOCK); + let shard1 = &*m1.borrow(); + let shard2 = &*m2.borrow(); + Ok((shard1.get()?, shard2.get()?)) + } + /// Returns the memory addresses of the two inner shards. /// /// This is for testing purposes only, and is intended to work with `NCConfig::FullRam` @@ -205,16 +165,75 @@ impl NonContiguousMemory { let a = &*muta.borrow(); let b = &*mutb.borrow(); - if let (MemoryShard::RamShard(a), MemoryShard::RamShard(b)) = (a, b) { - let a_ptr = a.get_ptr_address(); - let b_ptr = b.get_ptr_address(); + let (a_ptr, b_ptr) = match (a, b) { + (Ram(a), Ram(b)) => (a.get_ptr_address(), b.get_ptr_address()), + (Frag(a), Frag(b)) => ( + a.get()? as *const [u8; NC_DATA_SIZE] as usize, + b.get()? as *const [u8; NC_DATA_SIZE] as usize, + ), + _ => { + return Err(MemoryError::Allocation( + "Cannot get pointers. Unsupported MemoryShard configuration".to_owned(), + )); + } + }; + + Ok((a_ptr, b_ptr)) + } +} + +impl MemoryShard { + fn new_shards(data1: &[u8], data2: &[u8], config: &NCConfig) -> Result<(Self, Self), MemoryError> { + match config { + RamAndFile => { + let ram = RamMemory::alloc(data1, NC_DATA_SIZE)?; + let fmem = FileMemory::alloc(data2, NC_DATA_SIZE)?; + Ok((Ram(ram), File(fmem))) + } + + FullRam => { + let ram1 = RamMemory::alloc(data1, NC_DATA_SIZE)?; + let ram2 = RamMemory::alloc(data2, NC_DATA_SIZE)?; + Ok((Ram(ram1), Ram(ram2))) + } + + FullFile => { + let fmem1 = FileMemory::alloc(data1, NC_DATA_SIZE)?; + let fmem2 = FileMemory::alloc(data2, NC_DATA_SIZE)?; + Ok((File(fmem1), File(fmem2))) + } - return Ok((a_ptr, b_ptr)); + FragAllocation(strat) => { + let (frag1, frag2) = Frag::alloc( + *strat, + data1.try_into().map_err(|_| MemoryError::NCSizeNotAllowed)?, + data2.try_into().map_err(|_| MemoryError::NCSizeNotAllowed)?, + )?; + Ok((Frag(frag1), Frag(frag2))) + } } + } - Err(MemoryError::Allocation( - "Cannot get pointers. Unsupported MemoryShard configuration".to_owned(), - )) + fn get(&self) -> Result, MemoryError> { + match self { + File(fm) => { + let buf = fm.unlock()?; + let v = buf.borrow().to_vec(); + Ok(v) + } + Ram(ram) => { + let buf = ram.unlock()?; + let v = buf.borrow().to_vec(); + Ok(v) + } + Frag(frag) => { + if frag.is_live() { + Ok(frag.get()?.to_vec()) + } else { + Err(IllegalZeroizedUsage) + } + } + } } } @@ -228,8 +247,9 @@ impl Debug for NonContiguousMemory { impl Zeroize for MemoryShard { fn zeroize(&mut self) { match self { - FileShard(fm) => fm.zeroize(), - RamShard(buf) => buf.zeroize(), + File(fm) => fm.zeroize(), + Ram(buf) => buf.zeroize(), + Frag(frag) => frag.zeroize(), } } } @@ -291,7 +311,7 @@ impl<'de> Visitor<'de> for NonContiguousMemoryVisitor { seq.push(e); } - let seq = NonContiguousMemory::alloc(seq.as_slice(), seq.len(), NC_CONFIGURATION) + let seq = NonContiguousMemory::alloc(seq.as_slice(), seq.len(), NC_CONFIGURATION_SERIALIZATION) .expect("Failed to allocate NonContiguousMemory during deserialization"); Ok(seq) @@ -312,143 +332,90 @@ mod tests { use super::*; + static ERR: &str = "Error while testing non-contiguous memory "; + + const NC_CONFIGS: [NCConfig; 6] = [ + FullFile, + RamAndFile, + FullRam, + FragAllocation(FragStrategy::Map), + FragAllocation(FragStrategy::Direct), + FragAllocation(FragStrategy::Hybrid), + ]; + #[test] - fn noncontiguous_refresh() { + fn test_ncm_refresh() { let data = random_vec(NC_DATA_SIZE); - let ncm = NonContiguousMemory::alloc(&data, NC_DATA_SIZE, RamAndFile); - - assert!(ncm.is_ok()); - let ncm = ncm.unwrap(); + for config in NC_CONFIGS { + println!("config: {:?}", config); + let ncm = NonContiguousMemory::alloc(&data, NC_DATA_SIZE, config).expect(ERR); + test_refresh(ncm, &data); + } + } - let shard1_before_refresh = ncm.get_buffer_from_shard1(); - let shard2_before_refresh = if let FileShard(fm) = &*ncm.shard2.lock().expect(POISONED_LOCK).borrow() { - fm.unlock().unwrap() - } else { - panic!("{}", IMPOSSIBLE_CASE) - }; + fn test_refresh(ncm: NonContiguousMemory, original_data: &[u8]) { + let (data1_before_refresh, data2_before_refresh) = ncm.get_shards_data().expect(ERR); assert!(ncm.refresh().is_ok()); - let shard1_after_refresh = ncm.get_buffer_from_shard1(); - let shard2_after_refresh = if let FileShard(fm) = &*ncm.shard2.lock().expect(POISONED_LOCK).borrow() { - fm.unlock().unwrap() - } else { - panic!("{}", IMPOSSIBLE_CASE) - }; + let (data1_after_refresh, data2_after_refresh) = ncm.get_shards_data().expect(ERR); // Check that secrets is still ok after refresh let buf = ncm.unlock(); assert!(buf.is_ok()); let buf = buf.unwrap(); - assert_eq!((&*buf.borrow()), &data); + assert_eq!((*buf.borrow()), *original_data); - // Check that refresh change the shards - assert_ne!(&*shard1_before_refresh.borrow(), &*shard1_after_refresh.borrow()); - assert_ne!(&*shard2_before_refresh.borrow(), &*shard2_after_refresh.borrow()); + // Check that refresh have changed the shards + assert_ne!(data1_before_refresh, data1_after_refresh); + assert_ne!(data2_before_refresh, data2_after_refresh); } #[test] // Checking that the shards don't contain the data - fn boojum_security() { - // With full Ram - let data = random_vec(NC_DATA_SIZE); - let ncm = NonContiguousMemory::alloc(&data, NC_DATA_SIZE, FullRam); - assert!(ncm.is_ok()); - let ncm = ncm.unwrap(); - - if let RamShard(ram1) = &*ncm.shard1.lock().expect(POISONED_LOCK).borrow() { - let buf = ram1.unlock().unwrap(); - assert_ne!(&*buf.borrow(), &data); - } - if let RamShard(ram2) = &*ncm.shard2.lock().expect(POISONED_LOCK).borrow() { - let buf = ram2.unlock().unwrap(); - assert_ne!(&*buf.borrow(), &data); - } - - // With Ram and File - let data = random_vec(NC_DATA_SIZE); - let ncm = NonContiguousMemory::alloc(&data, NC_DATA_SIZE, RamAndFile); - - assert!(ncm.is_ok()); - let ncm = ncm.unwrap(); - - if let RamShard(ram1) = &*ncm.shard1.lock().expect(POISONED_LOCK).borrow() { - let buf = ram1.unlock().unwrap(); - assert_ne!(&*buf.borrow(), &data); + fn test_ncm_boojum_security() { + let original_data = random_vec(NC_DATA_SIZE); + for config in NC_CONFIGS { + let ncm = NonContiguousMemory::alloc(&original_data, NC_DATA_SIZE, config).expect(ERR); + let (data1, data2) = ncm.get_shards_data().expect(ERR); + assert_ne!(data1, original_data); + assert_ne!(data2, original_data); } - - if let FileShard(fm) = &*ncm.shard2.lock().expect(POISONED_LOCK).borrow() { - let buf = fm.unlock().unwrap(); - assert_ne!(&*buf.borrow(), &data); - }; } #[test] - fn noncontiguous_zeroize() { - // Check alloc + fn test_ncm_zeroize() { let data = random_vec(NC_DATA_SIZE); - let ncm = NonContiguousMemory::alloc(&data, NC_DATA_SIZE, RamAndFile); - - assert!(ncm.is_ok()); - let mut ncm = ncm.unwrap(); - ncm.zeroize(); - - if let RamShard(ram1) = &*ncm.shard1.lock().expect(POISONED_LOCK).borrow() { - assert!(ram1.unlock().is_err()); + for config in NC_CONFIGS { + let mut ncm = NonContiguousMemory::alloc(&data, NC_DATA_SIZE, config).expect(ERR); + ncm.zeroize(); + assert!(ncm.get_shards_data().is_err()); } - - if let FileShard(fm) = &*ncm.shard2.lock().expect(POISONED_LOCK).borrow() { - assert!(fm.unlock().is_err()); - }; } #[test] - fn test_nc_with_alloc() { - use random::Rng; - - // Usual size for a page - let threshold = 0x1000; - let mut payload = [0u8; NC_DATA_SIZE]; - let mut rng = random::thread_rng(); - assert!(rng.try_fill(&mut payload).is_ok(), "Error filling payload bytes"); - - let nc = NonContiguousMemory::alloc(&payload, NC_DATA_SIZE, NCConfig::FullRam); - assert!(nc.is_ok(), "Failed to allocated nc memory"); - - let ptrs = nc.unwrap().get_ptr_addresses(); - assert!(ptrs.is_ok()); - - let (a, b) = ptrs.unwrap(); - let distance = a.abs_diff(b); - assert!( - distance >= threshold, - "Pointer distance below threshold: 0x{:08X}", - distance - ); - } + fn test_distance_between_shards() { + // NCM configurations which are full ram + let configs = [ + FullRam, + FragAllocation(FragStrategy::Map), + FragAllocation(FragStrategy::Direct), + FragAllocation(FragStrategy::Hybrid), + ]; + let data = random_vec(NC_DATA_SIZE); - // This test is relevant only if the implemented policy is to refresh shards every time we unlock NC memory - #[test] - fn test_refresh_on_unlock() { - use random::Rng; - let mut payload = [0u8; NC_DATA_SIZE]; - let mut rng = random::thread_rng(); - assert!(rng.try_fill(&mut payload).is_ok(), "Error filling payload bytes"); - - let nc = NonContiguousMemory::alloc(&payload, NC_DATA_SIZE, NCConfig::FullRam); - assert!(nc.is_ok(), "Failed to allocated nc memory"); - let nc = nc.unwrap(); - - let ptrs = nc.get_ptr_addresses(); - assert!(ptrs.is_ok()); - let (a, b) = ptrs.unwrap(); - - assert!(nc.unlock().is_ok()); - let ptrs = nc.get_ptr_addresses(); - assert!(ptrs.is_ok()); - let (new_a, new_b) = ptrs.unwrap(); - - assert_ne!(a, new_a); - assert_ne!(b, new_b); + for config in configs { + let ncm = NonContiguousMemory::alloc(&data, NC_DATA_SIZE, config); + assert!(ncm.is_ok(), "Failed to allocated nc memory"); + let ptrs = ncm.unwrap().get_ptr_addresses(); + assert!(ptrs.is_ok()); + let (a, b) = ptrs.unwrap(); + let distance = a.abs_diff(b); + assert!( + distance >= crate::memories::frag::FRAG_MIN_DISTANCE, + "Pointer distance below threshold: 0x{:08X}", + distance + ); + } } } diff --git a/engine/runtime/tests/alloc_tests.rs b/engine/runtime/tests/frags_tests.rs similarity index 53% rename from engine/runtime/tests/alloc_tests.rs rename to engine/runtime/tests/frags_tests.rs index 7ce99f5a2..eff677fc0 100644 --- a/engine/runtime/tests/alloc_tests.rs +++ b/engine/runtime/tests/frags_tests.rs @@ -4,7 +4,7 @@ use log::*; use runtime::{ - memories::frag::{Frag, FragStrategy}, + memories::frag::{Frag, FragStrategy, FRAG_MIN_DISTANCE}, MemoryError, }; use std::fmt::Debug; @@ -25,19 +25,12 @@ impl Default for TestStruct { } #[test] -fn test_allocate_direct() { - test_allocate_strategy(FragStrategy::Direct) +fn test_fragments_allocate() { + test_allocate_strategy(FragStrategy::Direct); + test_allocate_strategy(FragStrategy::Map); + test_allocate_strategy(FragStrategy::Hybrid); } -#[test] -fn test_allocate_map() { - test_allocate_strategy(FragStrategy::Map) -} - -#[test] -fn test_allocate_hybrid() { - test_allocate_strategy(FragStrategy::Hybrid) -} fn test_allocate_strategy(strat: FragStrategy) { let _ = env_logger::builder() @@ -46,34 +39,55 @@ fn test_allocate_strategy(strat: FragStrategy) { .try_init(); info!("Test Fixed Distance"); - assert!(test_allocate::(|| Frag::alloc(strat)).is_ok()); + assert!(test_allocate::(|| Frag::alloc(strat, TestStruct::default(), TestStruct::default())).is_ok()); info!("Test Arbitrary Distance"); assert!(test_allocate::(|| Frag::alloc2(strat, 0xFFFF)).is_ok()); } +#[test] +fn test_fragments_deallocate() { + test_deallocate(FragStrategy::Direct); + test_deallocate(FragStrategy::Map); + test_deallocate(FragStrategy::Hybrid); +} + +fn test_deallocate(strat: FragStrategy) { + let _ = env_logger::builder() + .is_test(true) + .filter(None, log::LevelFilter::Info) + .try_init(); + + info!("Test Fixed Distance"); + let frags = Frag::alloc(strat, TestStruct::default(), TestStruct::default()); + assert!(frags.is_ok()); + let (mut f1, mut f2) = frags.unwrap(); + + assert!(Frag::dealloc(&mut f1).is_ok()); + assert!(Frag::dealloc(&mut f2).is_ok()); + + // To avoid double deallocation + std::mem::forget(f1); + std::mem::forget(f2); +} fn test_allocate(allocator: F) -> Result<(), MemoryError> where - T: Default + Debug + PartialEq, + T: Default + Debug + PartialEq + Clone, F: Fn() -> Result<(Frag, Frag), MemoryError>, { - let min_distance = 0xFFFF; let result = allocator(); assert!(result.is_ok(), "Failed to allocate memory"); let (a, b) = result.unwrap(); - let aa = &*a; - let bb = &*b; + let aa = a.get()?; + let bb = b.get()?; - assert!(distance(aa, bb) >= min_distance); + assert!(distance(aa, bb) >= FRAG_MIN_DISTANCE); assert_eq!(aa, &T::default()); assert_eq!(bb, &T::default()); - assert!(Frag::::dealloc(a).is_ok()); - assert!(Frag::::dealloc(b).is_ok()); - Ok(()) } diff --git a/engine/runtime/tests/memory_leak_test.rs b/engine/runtime/tests/memory_leak_test.rs index ebb6b4906..d3d7328b6 100644 --- a/engine/runtime/tests/memory_leak_test.rs +++ b/engine/runtime/tests/memory_leak_test.rs @@ -32,13 +32,13 @@ fn test_memory_leak_without_dealloc() { #[test] fn test_memory_no_leak() { - let frags = alloc_frags(FragStrategy::Direct, NB_ALLOC); - dealloc_frags(frags); + alloc_frags(FragStrategy::Direct, NB_ALLOC); } // Test to check the dhat tool // TODO: Not working yet +#[allow(dead_code)] fn test_dhat() { let _profiler = dhat::Profiler::builder().testing().build(); @@ -80,7 +80,7 @@ fn test_dhat() { fn alloc_frags(strat: FragStrategy, nb_alloc: usize) -> Vec> { let mut v = vec![]; for _ in 0..nb_alloc { - let frags = Frag::::alloc(strat); + let frags = Frag::::alloc(strat, TestStruct::default(), TestStruct::default()); assert!(frags.is_ok()); let (f1, f2) = frags.unwrap(); v.push(f1); @@ -88,9 +88,3 @@ fn alloc_frags(strat: FragStrategy, nb_alloc: usize) -> Vec> { } v } - -fn dealloc_frags(vec: Vec>) { - for frag in vec.into_iter() { - assert!(Frag::dealloc(frag).is_ok()); - } -} From b8d6995516ebb30dfe3590393916c0128b202252 Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Wed, 6 Jul 2022 10:45:08 +0200 Subject: [PATCH 36/46] Implement Send and Sync for Frag memory --- engine/runtime/src/memories/frag.rs | 3 +++ engine/runtime/src/memories/noncontiguous_memory.rs | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 281c1c5fd..947335e00 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -91,6 +91,9 @@ impl Drop for Frag { } } +unsafe impl Send for Frag {} +unsafe impl Sync for Frag {} + /// Configuration for the fragmenting allocator pub struct FragConfig { /// The last address of a previous allocation. This diff --git a/engine/runtime/src/memories/noncontiguous_memory.rs b/engine/runtime/src/memories/noncontiguous_memory.rs index 7809c6865..dccfc21ba 100644 --- a/engine/runtime/src/memories/noncontiguous_memory.rs +++ b/engine/runtime/src/memories/noncontiguous_memory.rs @@ -40,7 +40,7 @@ static POISONED_LOCK: &str = "NonContiguousMemory potentially in an unsafe state pub const NC_DATA_SIZE: usize = 32; // For serialization/deserialization we choose this fullram config -pub const NC_CONFIGURATION_SERIALIZATION: NCConfig = FullRam; +pub const NC_CONFIGURATION: NCConfig = FullRam; #[derive(Debug, PartialEq, Eq, Clone)] pub enum NCConfig { @@ -311,7 +311,7 @@ impl<'de> Visitor<'de> for NonContiguousMemoryVisitor { seq.push(e); } - let seq = NonContiguousMemory::alloc(seq.as_slice(), seq.len(), NC_CONFIGURATION_SERIALIZATION) + let seq = NonContiguousMemory::alloc(seq.as_slice(), seq.len(), NC_CONFIGURATION) .expect("Failed to allocate NonContiguousMemory during deserialization"); Ok(seq) @@ -327,6 +327,7 @@ impl<'de> Deserialize<'de> for NonContiguousMemory { } } + #[cfg(test)] mod tests { From 2e2905541b3645cd1081e9f5c771511241daeacb Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Wed, 6 Jul 2022 13:16:30 +0200 Subject: [PATCH 37/46] Write benchmarks for noncontiguous memories --- engine/benches/engine_bench.rs | 51 --------------- engine/runtime/Cargo.toml | 7 +- engine/runtime/benches/runtime_bench.rs | 87 +++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 52 deletions(-) create mode 100644 engine/runtime/benches/runtime_bench.rs diff --git a/engine/benches/engine_bench.rs b/engine/benches/engine_bench.rs index 8dc76e5f2..3d6c5be05 100644 --- a/engine/benches/engine_bench.rs +++ b/engine/benches/engine_bench.rs @@ -11,7 +11,6 @@ use engine::{ store::Cache, vault::{DbView, Key, RecordHint, RecordId, VaultId}, }; -use runtime::memories::frag::{Frag, FragStrategy}; use crate::provider::Provider; @@ -108,53 +107,6 @@ fn bench_store_decompress(c: &mut Criterion) { }); } -#[allow(dead_code)] -struct TestStruct { - id: usize, - name: String, -} - -impl Default for TestStruct { - fn default() -> Self { - Self { - id: 0xFFFF_FFFF_FFFF_FFFF, - name: "Some TestingStruct".to_owned(), - } - } -} - -fn bench_allocate_direct(c: &mut Criterion) { - bench_allocate_strat(c, FragStrategy::Direct); -} - -fn bench_allocate_mapped(c: &mut Criterion) { - bench_allocate_strat(c, FragStrategy::Map); -} - -fn bench_allocate_hybrid(c: &mut Criterion) { - bench_allocate_strat(c, FragStrategy::Hybrid); -} - -fn bench_allocate_strat(c: &mut Criterion, strat: FragStrategy) { - let bench_name = match strat { - FragStrategy::Direct => "Direct allocations", - FragStrategy::Map => "Map allocations", - FragStrategy::Hybrid => "Hybrid allocations", - _ => unreachable!(), - }; - - c.bench_function(bench_name, |b| { - b.iter(|| { - if let Ok((m1, m2)) = Frag::::alloc(strat) { - Frag::dealloc(m1).expect("error while deallocating"); - Frag::dealloc(m2).expect("error while deallocating"); - } else { - panic!("error while allocating memory"); - } - }); - }); -} - criterion_group!( benches, bench_snapshot_compression, @@ -164,8 +116,5 @@ criterion_group!( bench_store_compression, bench_store_decompress, bench_vault_write, - bench_allocate_direct, - bench_allocate_mapped, - bench_allocate_hybrid ); criterion_main!(benches); diff --git a/engine/runtime/Cargo.toml b/engine/runtime/Cargo.toml index 4bcdca9c1..be948e8b0 100644 --- a/engine/runtime/Cargo.toml +++ b/engine/runtime/Cargo.toml @@ -27,4 +27,9 @@ nix = { version = "0.24.1" } [dev-dependencies] serde_json = { version = "1.0" } env_logger = { version = "0.9" } -dhat = { version = "0.3" } \ No newline at end of file +dhat = { version = "0.3" } +criterion = "0.3.3" + +[[bench]] +name = "runtime_bench" +harness = false diff --git a/engine/runtime/benches/runtime_bench.rs b/engine/runtime/benches/runtime_bench.rs new file mode 100644 index 000000000..807be5c75 --- /dev/null +++ b/engine/runtime/benches/runtime_bench.rs @@ -0,0 +1,87 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + + +use criterion::{criterion_group, criterion_main, Criterion}; +use runtime::memories::frag::FragStrategy; +use runtime::memories::noncontiguous_memory::NCConfig::{self, *}; +use runtime::memories::noncontiguous_memory::*; +use runtime::utils::random_vec; +use runtime::locked_memory::LockedMemory; + +#[allow(dead_code)] +struct TestStruct { + id: usize, + name: String, +} + +impl Default for TestStruct { + fn default() -> Self { + Self { + id: 0xFFFF_FFFF_FFFF_FFFF, + name: "Some TestingStruct".to_owned(), + } + } +} + +fn bench_ncm_full_ram(c: &mut Criterion) { + bench_ncm(c, FullRam); +} + +fn bench_ncm_full_file(c: &mut Criterion) { + bench_ncm(c, FullFile); +} + +fn bench_ncm_ram_file(c: &mut Criterion) { + bench_ncm(c, RamAndFile); +} + +fn bench_ncm_frag_direct(c: &mut Criterion) { + bench_ncm(c, FragAllocation(FragStrategy::Direct)); +} + +fn bench_ncm_frag_map(c: &mut Criterion) { + bench_ncm(c, FragAllocation(FragStrategy::Map)); +} + +fn bench_ncm_frag_hybrid(c: &mut Criterion) { + bench_ncm(c, FragAllocation(FragStrategy::Hybrid)); +} + +fn bench_ncm(c: &mut Criterion, config: NCConfig) { + let bench_name = match config { + FullRam => "NCM full ram", + FullFile => "NCM full file", + RamAndFile => "NCM ram and file", + FragAllocation(frag) => match frag { + FragStrategy::Direct => "NCM fragment direct", + FragStrategy::Map => "NCM fragment map", + FragStrategy::Hybrid => "NCM fragment hybrid", + } + }; + + let data = random_vec(NC_DATA_SIZE); + + c.bench_function(bench_name, |b| { + b.iter(|| { + // Allocate + let ncm = NonContiguousMemory::alloc(&data, NC_DATA_SIZE, config.clone()).expect("error while allocating"); + + // Unlock non-contiguous memory few times to refresh the shards + for _ in 0..10 { + let _ = ncm.unlock().expect("error while unlocking"); + } + }); + }); +} + +criterion_group!( + benches, + bench_ncm_full_ram, + bench_ncm_full_file, + bench_ncm_ram_file, + bench_ncm_frag_direct, + bench_ncm_frag_map, + bench_ncm_frag_hybrid, +); +criterion_main!(benches); From 54632be3677c8c7650b7d413a7b8194d7f6524b7 Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Wed, 6 Jul 2022 14:15:31 +0200 Subject: [PATCH 38/46] Format code with clippy and cargo nightly --- engine/runtime/benches/runtime_bench.rs | 19 +++++++---- engine/runtime/src/memories/frag.rs | 20 +++++------- .../src/memories/noncontiguous_memory.rs | 1 - engine/runtime/tests/frags_tests.rs | 6 ++-- engine/runtime/tests/memory_leak_test.rs | 32 ++++++++++++------- 5 files changed, 43 insertions(+), 35 deletions(-) diff --git a/engine/runtime/benches/runtime_bench.rs b/engine/runtime/benches/runtime_bench.rs index 807be5c75..36a3c3c36 100644 --- a/engine/runtime/benches/runtime_bench.rs +++ b/engine/runtime/benches/runtime_bench.rs @@ -1,13 +1,18 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 - use criterion::{criterion_group, criterion_main, Criterion}; -use runtime::memories::frag::FragStrategy; -use runtime::memories::noncontiguous_memory::NCConfig::{self, *}; -use runtime::memories::noncontiguous_memory::*; -use runtime::utils::random_vec; -use runtime::locked_memory::LockedMemory; +use runtime::{ + locked_memory::LockedMemory, + memories::{ + frag::FragStrategy, + noncontiguous_memory::{ + NCConfig::{self, *}, + *, + }, + }, + utils::random_vec, +}; #[allow(dead_code)] struct TestStruct { @@ -57,7 +62,7 @@ fn bench_ncm(c: &mut Criterion, config: NCConfig) { FragStrategy::Direct => "NCM fragment direct", FragStrategy::Map => "NCM fragment map", FragStrategy::Hybrid => "NCM fragment hybrid", - } + }, }; let data = random_vec(NC_DATA_SIZE); diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 947335e00..9ebedb73b 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -18,10 +18,7 @@ use crate::MemoryError; use log::*; -use std::{ - fmt::Debug, - ptr::NonNull, -}; +use std::{fmt::Debug, ptr::NonNull}; use zeroize::Zeroize; // The minimum distance we consider between 2 fragments @@ -163,7 +160,8 @@ impl Frag { Ok((a, b)) } - /// Tries to allocate two objects of the same type with a default minimum distance in memory space of `FRAG_MIN_DISTANCE`. + /// Tries to allocate two objects of the same type with a default minimum distance in memory space of + /// `FRAG_MIN_DISTANCE`. pub fn alloc(strategy: FragStrategy, data1: T, data2: T) -> Result<(Frag, Frag), MemoryError> { let (mut f1, mut f2) = Self::alloc2(strategy, 100 * FRAG_MIN_DISTANCE)?; f1.set(data1)?; @@ -183,18 +181,16 @@ impl Frag { self.live } - pub fn get(&self) -> Result<&T, MemoryError>{ + pub fn get(&self) -> Result<&T, MemoryError> { if !self.live { - return Err(MemoryError::IllegalZeroizedUsage) - } - unsafe { - Ok(self.ptr.as_ref()) + return Err(MemoryError::IllegalZeroizedUsage); } + unsafe { Ok(self.ptr.as_ref()) } } - pub fn set(&mut self, data: T) -> Result<(), MemoryError>{ + pub fn set(&mut self, data: T) -> Result<(), MemoryError> { if !self.live { - return Err(MemoryError::IllegalZeroizedUsage) + return Err(MemoryError::IllegalZeroizedUsage); } unsafe { let ptr: *mut T = self.ptr.as_mut(); diff --git a/engine/runtime/src/memories/noncontiguous_memory.rs b/engine/runtime/src/memories/noncontiguous_memory.rs index dccfc21ba..b99d1606c 100644 --- a/engine/runtime/src/memories/noncontiguous_memory.rs +++ b/engine/runtime/src/memories/noncontiguous_memory.rs @@ -327,7 +327,6 @@ impl<'de> Deserialize<'de> for NonContiguousMemory { } } - #[cfg(test)] mod tests { diff --git a/engine/runtime/tests/frags_tests.rs b/engine/runtime/tests/frags_tests.rs index eff677fc0..c16392a25 100644 --- a/engine/runtime/tests/frags_tests.rs +++ b/engine/runtime/tests/frags_tests.rs @@ -31,7 +31,6 @@ fn test_fragments_allocate() { test_allocate_strategy(FragStrategy::Hybrid); } - fn test_allocate_strategy(strat: FragStrategy) { let _ = env_logger::builder() .is_test(true) @@ -39,7 +38,9 @@ fn test_allocate_strategy(strat: FragStrategy) { .try_init(); info!("Test Fixed Distance"); - assert!(test_allocate::(|| Frag::alloc(strat, TestStruct::default(), TestStruct::default())).is_ok()); + assert!( + test_allocate::(|| Frag::alloc(strat, TestStruct::default(), TestStruct::default())).is_ok() + ); info!("Test Arbitrary Distance"); assert!(test_allocate::(|| Frag::alloc2(strat, 0xFFFF)).is_ok()); @@ -91,7 +92,6 @@ where Ok(()) } - // ---------------------------------------------------------------------------- /// Calculates the distance between two pointers diff --git a/engine/runtime/tests/memory_leak_test.rs b/engine/runtime/tests/memory_leak_test.rs index d3d7328b6..c95d0f14a 100644 --- a/engine/runtime/tests/memory_leak_test.rs +++ b/engine/runtime/tests/memory_leak_test.rs @@ -1,13 +1,13 @@ -use runtime::{ - memories::frag::{Frag, FragStrategy}, -}; +// Copyright 2020-2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use runtime::memories::frag::{Frag, FragStrategy}; use std::fmt::Debug; #[global_allocator] static ALLOC: dhat::Alloc = dhat::Alloc; - #[derive(PartialEq, Debug, Clone)] struct TestStruct { id: usize, @@ -35,23 +35,27 @@ fn test_memory_no_leak() { alloc_frags(FragStrategy::Direct, NB_ALLOC); } - // Test to check the dhat tool // TODO: Not working yet #[allow(dead_code)] fn test_dhat() { - let _profiler = dhat::Profiler::builder().testing().build(); let stats = dhat::HeapStats::get(); dhat::assert_eq!(stats.curr_blocks, 0); - println!("0 - Blocks allocated: {}, bytes allocated: {}", stats.curr_blocks, stats.curr_bytes); + println!( + "0 - Blocks allocated: {}, bytes allocated: {}", + stats.curr_blocks, stats.curr_bytes + ); const SIZE: usize = 1000; let b = [5i32; SIZE]; let mut vec = Vec::with_capacity(10); - println!("0.5 - Blocks allocated: {}, bytes allocated: {}", stats.curr_blocks, stats.curr_bytes); + println!( + "0.5 - Blocks allocated: {}, bytes allocated: {}", + stats.curr_blocks, stats.curr_bytes + ); unsafe { for i in 0..10 { let ptr = libc::malloc(SIZE * 4) as *mut i32; @@ -60,13 +64,18 @@ fn test_dhat() { vec.push(ptr); let stats = dhat::HeapStats::get(); - println!("{} - Blocks allocated: {}, bytes allocated: {}", i, stats.curr_blocks, stats.curr_bytes); - + println!( + "{} - Blocks allocated: {}, bytes allocated: {}", + i, stats.curr_blocks, stats.curr_bytes + ); } let stats = dhat::HeapStats::get(); dhat::assert_eq!(stats.curr_blocks, 1); - println!("final - Blocks allocated: {}, bytes allocated: {}", stats.curr_blocks, stats.curr_bytes); + println!( + "final - Blocks allocated: {}, bytes allocated: {}", + stats.curr_blocks, stats.curr_bytes + ); dhat::assert_eq!(stats.curr_blocks, 0); // libc::free(ptr); @@ -75,7 +84,6 @@ fn test_dhat() { } } - // Goal fn alloc_frags(strat: FragStrategy, nb_alloc: usize) -> Vec> { let mut v = vec![]; From 6ef30cc77a743ef2a0f511df615509bf6c883656 Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Wed, 6 Jul 2022 18:13:05 +0200 Subject: [PATCH 39/46] Fix bugs in windows due to api change --- engine/runtime/src/memories/frag.rs | 10 +++------- engine/runtime/src/memories/noncontiguous_memory.rs | 8 +++----- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 9ebedb73b..87e436366 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -140,10 +140,6 @@ impl Frag { let actual_distance = calc_distance(a.get()?, b.get()?); if actual_distance < distance { - error!( - "Distance between parts below threshold: \nthreshold: 0x{:016X} \nactual_distance: 0x{:016X}", - distance, actual_distance - ); error!( "Distance between parts below threshold: \na: 0x{:016x} \nb: 0x{:016x} \ngiven_value: 0x{:016x}", a.ptr.as_ptr() as usize, @@ -163,7 +159,7 @@ impl Frag { /// Tries to allocate two objects of the same type with a default minimum distance in memory space of /// `FRAG_MIN_DISTANCE`. pub fn alloc(strategy: FragStrategy, data1: T, data2: T) -> Result<(Frag, Frag), MemoryError> { - let (mut f1, mut f2) = Self::alloc2(strategy, 100 * FRAG_MIN_DISTANCE)?; + let (mut f1, mut f2) = Self::alloc2(strategy, FRAG_MIN_DISTANCE)?; f1.set(data1)?; f2.set(data2)?; Ok((f1, f2)) @@ -363,7 +359,7 @@ where } #[cfg(target_os = "windows")] - fn dealloc(frag: Frag) -> Result<(), Self::Error> { + fn dealloc(frag: &mut Frag) -> Result<(), Self::Error> { if let Some((handle, view)) = frag.info { dealloc_map(handle, view) } else { @@ -502,7 +498,7 @@ where } #[cfg(target_os = "windows")] - fn dealloc(frag: Frag) -> Result<(), Self::Error> { + fn dealloc(frag: &mut Frag) -> Result<(), Self::Error> { dealloc_direct(NonNull::as_ptr(frag.ptr) as *mut libc::c_void) } } diff --git a/engine/runtime/src/memories/noncontiguous_memory.rs b/engine/runtime/src/memories/noncontiguous_memory.rs index b99d1606c..2b7396fe5 100644 --- a/engine/runtime/src/memories/noncontiguous_memory.rs +++ b/engine/runtime/src/memories/noncontiguous_memory.rs @@ -405,11 +405,9 @@ mod tests { let data = random_vec(NC_DATA_SIZE); for config in configs { - let ncm = NonContiguousMemory::alloc(&data, NC_DATA_SIZE, config); - assert!(ncm.is_ok(), "Failed to allocated nc memory"); - let ptrs = ncm.unwrap().get_ptr_addresses(); - assert!(ptrs.is_ok()); - let (a, b) = ptrs.unwrap(); + let ncm = NonContiguousMemory::alloc(&data, NC_DATA_SIZE, config).expect(ERR); + let ptrs = ncm.get_ptr_addresses().expect(ERR); + let (a, b) = ptrs; let distance = a.abs_diff(b); assert!( distance >= crate::memories::frag::FRAG_MIN_DISTANCE, From 4fbcdb273d456d8c08680c9c1bc5bd87929fc0b2 Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Fri, 8 Jul 2022 13:08:04 +0200 Subject: [PATCH 40/46] Small fix related to pull request comments --- engine/runtime/README.md | 10 ++-------- engine/runtime/src/memories/frag.rs | 12 ++++-------- engine/runtime/src/memories/noncontiguous_memory.rs | 2 +- engine/runtime/src/memories/ram_memory.rs | 10 ++++++++++ engine/runtime/tests/frags_tests.rs | 6 +++--- engine/runtime/tests/memory_leak_test.rs | 4 +++- 6 files changed, 23 insertions(+), 21 deletions(-) diff --git a/engine/runtime/README.md b/engine/runtime/README.md index 9b9e2a2ba..b446c1750 100644 --- a/engine/runtime/README.md +++ b/engine/runtime/README.md @@ -4,7 +4,6 @@ This crate provides multiple ways to store data securely whether in ram, disk or All these types of memories implement the `LockedMemory` trait which enables one to allocate or unlock the data stored. A `Buffer` type which implements basic security measures is also provided to temporarily store data for any computation. - ## `Buffer` Memory which contains some "minimal" security measures such as: - Guard areas @@ -78,13 +77,8 @@ Hence data security depends on the strength of the encryption scheme and the 'ob - [x] `NonContiguousMemory` - [ ] Tests - [x] Functional correctness - - [ ] Security + - [x] Security - [x] zeroize - [ ] access to the locked memory -- [ ] Benchmarks +- [x] Benchmarks - [ ] no-std - - -- -0x00007ffff7fc4fe0 -0x00007ffff7fc0fe0 \ No newline at end of file diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 87e436366..674951505 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -34,7 +34,7 @@ pub enum FragStrategy { /// Using system allocator (`malloc` on linux/bsd/macos and `VirtualAlloc` on windows) Direct, - /// Allocate using both strategy + /// Allocate using both strategies Hybrid, } @@ -122,9 +122,8 @@ impl Frag { /// /// let object = Frag::by_strategy(FragStrategy::Default).unwrap(); /// ``` - /// Tries to allocate two objects of the same type with a minimum distance in memory space. - pub fn alloc2(strategy: FragStrategy, distance: usize) -> Result<(Frag, Frag), MemoryError> { + pub fn alloc_default(strategy: FragStrategy, distance: usize) -> Result<(Frag, Frag), MemoryError> { let a = match strategy { FragStrategy::Direct => DirectAlloc::alloc(None), FragStrategy::Map => MemoryMapAlloc::alloc(None), @@ -158,8 +157,8 @@ impl Frag { /// Tries to allocate two objects of the same type with a default minimum distance in memory space of /// `FRAG_MIN_DISTANCE`. - pub fn alloc(strategy: FragStrategy, data1: T, data2: T) -> Result<(Frag, Frag), MemoryError> { - let (mut f1, mut f2) = Self::alloc2(strategy, FRAG_MIN_DISTANCE)?; + pub fn alloc_initialized(strategy: FragStrategy, data1: T, data2: T) -> Result<(Frag, Frag), MemoryError> { + let (mut f1, mut f2) = Self::alloc_default(strategy, FRAG_MIN_DISTANCE)?; f1.set(data1)?; f2.set(data2)?; Ok((f1, f2)) @@ -309,9 +308,6 @@ where #[cfg(target_os = "windows")] fn alloc(_config: Option) -> Result, Self::Error> { - // use random::thread_rng; - // let mut rng = thread_rng(); - let handle = windows::Win32::Foundation::INVALID_HANDLE_VALUE; loop { unsafe { diff --git a/engine/runtime/src/memories/noncontiguous_memory.rs b/engine/runtime/src/memories/noncontiguous_memory.rs index 2b7396fe5..f5c1f814a 100644 --- a/engine/runtime/src/memories/noncontiguous_memory.rs +++ b/engine/runtime/src/memories/noncontiguous_memory.rs @@ -204,7 +204,7 @@ impl MemoryShard { } FragAllocation(strat) => { - let (frag1, frag2) = Frag::alloc( + let (frag1, frag2) = Frag::alloc_initialized( *strat, data1.try_into().map_err(|_| MemoryError::NCSizeNotAllowed)?, data2.try_into().map_err(|_| MemoryError::NCSizeNotAllowed)?, diff --git a/engine/runtime/src/memories/ram_memory.rs b/engine/runtime/src/memories/ram_memory.rs index 5ff1f875b..cb71b4853 100644 --- a/engine/runtime/src/memories/ram_memory.rs +++ b/engine/runtime/src/memories/ram_memory.rs @@ -163,4 +163,14 @@ mod tests { assert!((*ram.buf.borrow()).is_empty()); assert!(ram.unlock().is_err()); } + + #[test] + fn toto() { + let ram = RamMemory::alloc(&[1, 2, 3, 4, 5, 6][..], 6).unwrap(); + let a1: usize = ram.get_ptr_address(); + println!("a1: {:?}", a1); + let a2: usize = std::ptr::addr_of!(ram.buf.boxed.ptr) as *const _ as usize; + println!("a2: {:?}", a2); + assert!(a1 == a2); + } } diff --git a/engine/runtime/tests/frags_tests.rs b/engine/runtime/tests/frags_tests.rs index c16392a25..429e033ed 100644 --- a/engine/runtime/tests/frags_tests.rs +++ b/engine/runtime/tests/frags_tests.rs @@ -39,11 +39,11 @@ fn test_allocate_strategy(strat: FragStrategy) { info!("Test Fixed Distance"); assert!( - test_allocate::(|| Frag::alloc(strat, TestStruct::default(), TestStruct::default())).is_ok() + test_allocate::(|| Frag::alloc_initialized(strat, TestStruct::default(), TestStruct::default())).is_ok() ); info!("Test Arbitrary Distance"); - assert!(test_allocate::(|| Frag::alloc2(strat, 0xFFFF)).is_ok()); + assert!(test_allocate::(|| Frag::alloc_default(strat, 0xFFFF)).is_ok()); } #[test] @@ -60,7 +60,7 @@ fn test_deallocate(strat: FragStrategy) { .try_init(); info!("Test Fixed Distance"); - let frags = Frag::alloc(strat, TestStruct::default(), TestStruct::default()); + let frags = Frag::alloc_initialized(strat, TestStruct::default(), TestStruct::default()); assert!(frags.is_ok()); let (mut f1, mut f2) = frags.unwrap(); diff --git a/engine/runtime/tests/memory_leak_test.rs b/engine/runtime/tests/memory_leak_test.rs index c95d0f14a..a0e1b8fae 100644 --- a/engine/runtime/tests/memory_leak_test.rs +++ b/engine/runtime/tests/memory_leak_test.rs @@ -26,11 +26,13 @@ impl Default for TestStruct { const NB_ALLOC: usize = 20; // We run this the test binary with valgrind to check for potential leak #[test] +#[ignore = "dhat tool not working"] fn test_memory_leak_without_dealloc() { let _ = alloc_frags(FragStrategy::Direct, NB_ALLOC); } #[test] +#[ignore = "dhat tool not working"] fn test_memory_no_leak() { alloc_frags(FragStrategy::Direct, NB_ALLOC); } @@ -88,7 +90,7 @@ fn test_dhat() { fn alloc_frags(strat: FragStrategy, nb_alloc: usize) -> Vec> { let mut v = vec![]; for _ in 0..nb_alloc { - let frags = Frag::::alloc(strat, TestStruct::default(), TestStruct::default()); + let frags = Frag::::alloc_initialized(strat, TestStruct::default(), TestStruct::default()); assert!(frags.is_ok()); let (f1, f2) = frags.unwrap(); v.push(f1); From 61a8a175bf89209de4e1343c11e8950581c42ba5 Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Fri, 8 Jul 2022 13:13:15 +0200 Subject: [PATCH 41/46] Remove code for testing --- engine/runtime/src/boxed.rs | 35 ++--------------------- engine/runtime/src/memories/ram_memory.rs | 10 ------- engine/runtime/tests/frags_tests.rs | 9 ++++-- 3 files changed, 8 insertions(+), 46 deletions(-) diff --git a/engine/runtime/src/boxed.rs b/engine/runtime/src/boxed.rs index b16ba3abf..452e4ffc5 100644 --- a/engine/runtime/src/boxed.rs +++ b/engine/runtime/src/boxed.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 use crate::types::*; -use random::Rng; use zeroize::Zeroize; use core::{ @@ -14,8 +13,8 @@ use core::{ }; use libsodium_sys::{ - sodium_allocarray, sodium_free, sodium_init, sodium_memzero, sodium_mlock, sodium_mprotect_noaccess, - sodium_mprotect_readonly, sodium_mprotect_readwrite, + sodium_allocarray, sodium_free, sodium_init, sodium_mlock, sodium_mprotect_noaccess, sodium_mprotect_readonly, + sodium_mprotect_readwrite, }; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -146,40 +145,10 @@ impl Boxed { if unsafe { sodium_init() == -1 } { panic!("Failed to initialize libsodium") } - let mut rng = random::thread_rng(); - - // TESTING - let chunk_a = unsafe { - let size = rng.gen_range(0x1000..0xFFFFFF); - let random_length = 1; - let ptr = sodium_allocarray(random_length, size); - sodium_memzero(ptr, size); - ptr - }; - - // END TESTING let ptr = NonNull::new(unsafe { sodium_allocarray(len, mem::size_of::()) as *mut _ }) .expect("Failed to allocate memory"); - // TESTING - unsafe { - sodium_free(chunk_a); - } - - let chunk_a = unsafe { - let size = rng.gen_range(0x1000..0xFFFFFF); - let random_length = 1; - let ptr = sodium_allocarray(random_length, size); - sodium_memzero(ptr, size); - ptr - }; - - unsafe { - sodium_free(chunk_a); - } - // END TESTING - Self { ptr, len, diff --git a/engine/runtime/src/memories/ram_memory.rs b/engine/runtime/src/memories/ram_memory.rs index cb71b4853..5ff1f875b 100644 --- a/engine/runtime/src/memories/ram_memory.rs +++ b/engine/runtime/src/memories/ram_memory.rs @@ -163,14 +163,4 @@ mod tests { assert!((*ram.buf.borrow()).is_empty()); assert!(ram.unlock().is_err()); } - - #[test] - fn toto() { - let ram = RamMemory::alloc(&[1, 2, 3, 4, 5, 6][..], 6).unwrap(); - let a1: usize = ram.get_ptr_address(); - println!("a1: {:?}", a1); - let a2: usize = std::ptr::addr_of!(ram.buf.boxed.ptr) as *const _ as usize; - println!("a2: {:?}", a2); - assert!(a1 == a2); - } } diff --git a/engine/runtime/tests/frags_tests.rs b/engine/runtime/tests/frags_tests.rs index 429e033ed..bd0951283 100644 --- a/engine/runtime/tests/frags_tests.rs +++ b/engine/runtime/tests/frags_tests.rs @@ -38,9 +38,12 @@ fn test_allocate_strategy(strat: FragStrategy) { .try_init(); info!("Test Fixed Distance"); - assert!( - test_allocate::(|| Frag::alloc_initialized(strat, TestStruct::default(), TestStruct::default())).is_ok() - ); + assert!(test_allocate::(|| Frag::alloc_initialized( + strat, + TestStruct::default(), + TestStruct::default() + )) + .is_ok()); info!("Test Arbitrary Distance"); assert!(test_allocate::(|| Frag::alloc_default(strat, 0xFFFF)).is_ok()); From 568c1a099212804b5add241a84e27c243302423c Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Mon, 11 Jul 2022 14:42:25 +0200 Subject: [PATCH 42/46] Format the code --- engine/runtime/src/memories/noncontiguous_memory.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/engine/runtime/src/memories/noncontiguous_memory.rs b/engine/runtime/src/memories/noncontiguous_memory.rs index 1c7edec2b..f5c1f814a 100644 --- a/engine/runtime/src/memories/noncontiguous_memory.rs +++ b/engine/runtime/src/memories/noncontiguous_memory.rs @@ -128,7 +128,6 @@ impl NonContiguousMemory { let random = random_vec(NC_DATA_SIZE); let (old_data1, old_data2) = self.get_shards_data()?; - let new_data1 = xor(&old_data1, &random, NC_DATA_SIZE); let hash_of_old_shard1 = &blake2b::Blake2b256::digest(&old_data1); @@ -176,7 +175,6 @@ impl NonContiguousMemory { return Err(MemoryError::Allocation( "Cannot get pointers. Unsupported MemoryShard configuration".to_owned(), )); - } }; From 0f4e7f036cafb9a7f478e593be1bcc7318f6cb01 Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Mon, 11 Jul 2022 14:54:18 +0200 Subject: [PATCH 43/46] Fix test in the doc --- engine/runtime/src/memories/frag.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 674951505..9ce1c3cfb 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -201,7 +201,6 @@ impl Frag { /// of this memory will be randomly seeded. /// /// The actual implementation is system dependent and might vary. -/// ``` #[derive(Default, Clone)] struct MemoryMapAlloc; From 6ef5d51a29db705b083ee857bed361f0a1895d63 Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Tue, 12 Jul 2022 18:44:32 +0200 Subject: [PATCH 44/46] Reformat code --- engine/runtime/src/memories/frag.rs | 355 +++++++++++++--------------- 1 file changed, 159 insertions(+), 196 deletions(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 9ce1c3cfb..1c50248f8 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -23,7 +23,8 @@ use zeroize::Zeroize; // The minimum distance we consider between 2 fragments // This is the page size for most linux systems -pub const FRAG_MIN_DISTANCE: usize = 0x1000; +pub static FRAG_MIN_DISTANCE: usize = 0x1000; +const MAX_RETRY_ATTEMPTS: usize = 10; /// Fragmenting strategy to allocate memory at random addresses. #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -97,17 +98,13 @@ pub struct FragConfig { /// value will be used to calculate the minimum distance to the /// previous allocation. pub(crate) last_address: usize, - - /// The minimum distance to a previous allocation - pub(crate) min_distance: usize, } impl FragConfig { /// Creates a new [`FragConfig`] - pub fn new(last_address: usize, min_distance: usize) -> Self { + pub fn new(last_address: usize) -> Self { Self { last_address, - min_distance, } } } @@ -130,29 +127,30 @@ impl Frag { FragStrategy::Hybrid => DirectAlloc::alloc(None), }?; - let config = Some(FragConfig::new(a.ptr.as_ptr() as usize, distance)); - let b = match strategy { - FragStrategy::Direct => DirectAlloc::alloc(config), - FragStrategy::Map => MemoryMapAlloc::alloc(config), - FragStrategy::Hybrid => MemoryMapAlloc::alloc(config), - }?; - - let actual_distance = calc_distance(a.get()?, b.get()?); - if actual_distance < distance { - error!( - "Distance between parts below threshold: \na: 0x{:016x} \nb: 0x{:016x} \ngiven_value: 0x{:016x}", - a.ptr.as_ptr() as usize, - b.ptr.as_ptr() as usize, - &a as *const _ as usize - ); - return Err(MemoryError::Allocation(format!( - "Distance between parts below threshold: 0x{:016X}", - actual_distance - ))); + for _ in 0..MAX_RETRY_ATTEMPTS { + let config = Some(FragConfig::new(a.ptr.as_ptr() as usize)); + let mut b = match strategy { + FragStrategy::Direct => DirectAlloc::alloc(config), + FragStrategy::Map => MemoryMapAlloc::alloc(config), + FragStrategy::Hybrid => MemoryMapAlloc::alloc(config), + }?; + + let actual_distance = calc_distance(a.get()?, b.get()?); + if actual_distance < distance { + error!( + "Distance between parts below threshold: \na: 0x{:016x} \nb: 0x{:016x} \ngiven_value: 0x{:016x}", + a.ptr.as_ptr() as usize, + b.ptr.as_ptr() as usize, + &a as *const _ as usize + ); + Frag::dealloc(&mut b)?; + continue; + } + info!("Mapped 2 fragments: at {:?} and {:?}", a.ptr, b.ptr); + return Ok((a, b)); } - info!("Mapped 2 fragments: at {:?} and {:?}", a.ptr, b.ptr); - Ok((a, b)) + Err(MemoryError::Allocation(format!("Unable to allocate safe fragments"))) } /// Tries to allocate two objects of the same type with a default minimum distance in memory space of @@ -229,73 +227,65 @@ where info!("Using page size {}", pagesize); unsafe { - loop { - let mut addr: usize = (rng.gen::() >> 32) & (!0usize ^ (pagesize - 1)); - - info!("Desired addr 0x{:08X}", addr); - - // the maximum size of the mapping - let max_alloc_size = 0xFFFFFF; - - let desired_alloc_size: usize = rng.gen_range(size..=max_alloc_size); - - info!("prealloc: desired alloc size 0x{:08X}", desired_alloc_size); - - // this creates an anonymous mapping zeroed out. - let c_ptr = libc::mmap( - &mut addr as *mut _ as *mut libc::c_void, - desired_alloc_size, - libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_ANONYMOUS | libc::MAP_PRIVATE, - -1, - 0, - ); - - info!("Preallocated segment addr: {:p}", c_ptr); + let mut addr: usize = if let Some(cfg) = config { + let offset = rng.gen_range(100..10000) * pagesize; + cfg.last_address + offset + } else { + 0 + }; + + info!("Desired addr 0x{:08X}", addr); + + // the maximum size of the mapping + let max_alloc_size = 0xFFFFFF; + + let desired_alloc_size: usize = rng.gen_range(size..=max_alloc_size); + + info!("prealloc: desired alloc size 0x{:08X}", desired_alloc_size); + + // this creates an anonymous mapping zeroed out. + let c_ptr = libc::mmap( + &mut addr as *mut _ as *mut libc::c_void, + desired_alloc_size, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_ANONYMOUS | libc::MAP_PRIVATE, + -1, + 0, + ); - if c_ptr == libc::MAP_FAILED { - warn!("Memory mapping failed"); - continue; - } + info!("Preallocated segment addr: {:p}", c_ptr); - // Check that new memory is far enough - if let Some(ref cfg) = config { - let actual_distance = (c_ptr as usize).abs_diff(cfg.last_address); - if actual_distance < cfg.min_distance { - warn!("New allocation distance to previous allocation is below threshold."); - dealloc_map(c_ptr, desired_alloc_size)?; - continue; - } - } + if c_ptr == libc::MAP_FAILED { + return Err(MemoryError::Allocation(format!("Memory mapping failed"))); + } - #[cfg(any(target_os = "macos"))] + #[cfg(any(target_os = "macos"))] + { + // on linux this isn't required to commit memory + let error = libc::madvise(&mut addr as *mut usize as *mut libc::c_void, size, libc::MADV_WILLNEED); { - // on linux this isn't required to commit memory - let error = libc::madvise(&mut addr as *mut usize as *mut libc::c_void, size, libc::MADV_WILLNEED); - - { - if error != 0 { - error!("madvise returned an error {}", error); - continue; - } + if error != 0 { + return Err(MemoryError::Allocation(format!("madvise returned an error {}", error))); } } + } - let ptr = c_ptr as *mut T; + let ptr = c_ptr as *mut T; - if !ptr.is_null() { - let t = T::default(); - ptr.write(t); + if !ptr.is_null() { + let t = T::default(); + ptr.write(t); - info!("Object succesfully written into mem location"); + info!("Object succesfully written into mem location"); - return Ok(Frag { - ptr: NonNull::new_unchecked(ptr), - strategy: FragStrategy::Map, - live: true, - info: (c_ptr, desired_alloc_size), - }); - } + Ok(Frag { + ptr: NonNull::new_unchecked(ptr), + strategy: FragStrategy::Map, + live: true, + info: (c_ptr, desired_alloc_size), + }) + } else { + Err(MemoryError::Allocation(format!("Received a null pointer"))) } } } @@ -308,48 +298,43 @@ where #[cfg(target_os = "windows")] fn alloc(_config: Option) -> Result, Self::Error> { let handle = windows::Win32::Foundation::INVALID_HANDLE_VALUE; - loop { - unsafe { - // actual memory mapping - { - let actual_size = std::mem::size_of::() as u32; - let actual_mapping = windows::Win32::System::Memory::CreateFileMappingW( - handle, - std::ptr::null_mut(), - windows::Win32::System::Memory::PAGE_READWRITE, - 0, - actual_size, - windows::core::PCWSTR(std::ptr::null_mut()), - ) - .map_err(|e| MemoryError::Allocation(e.to_string()))?; - - if let Err(e) = last_error() { - return Err(e); - } + unsafe { + let actual_size = std::mem::size_of::() as u32; + let actual_mapping = windows::Win32::System::Memory::CreateFileMappingW( + handle, + std::ptr::null_mut(), + windows::Win32::System::Memory::PAGE_READWRITE, + 0, + actual_size, + windows::core::PCWSTR(std::ptr::null_mut()), + ) + .map_err(|e| MemoryError::Allocation(e.to_string()))?; - let mem_view = windows::Win32::System::Memory::MapViewOfFile( - actual_mapping, - windows::Win32::System::Memory::FILE_MAP_ALL_ACCESS, - 0, - 0, - actual_size as usize, - ); - let actual_mem = mem_view as *mut T; - - if let Err(e) = last_error() { - return Err(e); - } + if let Err(e) = last_error() { + return Err(e); + } - actual_mem.write(T::default()); + let mem_view = windows::Win32::System::Memory::MapViewOfFile( + actual_mapping, + windows::Win32::System::Memory::FILE_MAP_ALL_ACCESS, + 0, + 0, + actual_size as usize, + ); + let actual_mem = mem_view as *mut T; - return Ok(Frag { - ptr: NonNull::new_unchecked(actual_mem), - strategy: FragStrategy::Map, - live: true, - info: Some((actual_mapping, mem_view)), - }); - } + if let Err(e) = last_error() { + return Err(e); } + + actual_mem.write(T::default()); + + return Ok(Frag { + ptr: NonNull::new_unchecked(actual_mem), + strategy: FragStrategy::Map, + live: true, + info: Some((actual_mapping, mem_view)), + }); } } @@ -379,7 +364,7 @@ where type Error = MemoryError; #[cfg(any(target_os = "linux", target_os = "macos"))] - fn alloc(config: Option) -> Result, Self::Error> { + fn alloc(_config: Option) -> Result, Self::Error> { use random::{thread_rng, Rng}; let mut rng = thread_rng(); @@ -395,53 +380,42 @@ where .unwrap_or(Some(default_page_size)) .unwrap() as usize; - // Within the loop we allocate a sufficiently "large" chunk of memory. A random - // offset will be added to the returned pointer and the object will be written. This - // actually leaks memory. - loop { - unsafe { - let alloc_size = rng.gen::().min(min).max(max); - let mem_ptr = { - // allocate some randomly sized chunk of memory - let c_ptr = libc::malloc(alloc_size); - if c_ptr.is_null() { - continue; - } - - #[cfg(target_os = "macos")] - { - // on linux it isn't required to commit memory - let error = libc::madvise(c_ptr, actual_size, libc::MADV_WILLNEED); - if error != 0 { - error!("memory advise returned an error {}", error); - continue; - } - } - - c_ptr - }; + // We allocate a sufficiently "large" chunk of memory. A random + // offset will be added to the returned pointer and the object will be written. + unsafe { + let alloc_size = rng.gen::().min(min).max(max); + let mem_ptr = { + // allocate some randomly sized chunk of memory + let c_ptr = libc::malloc(alloc_size); + if c_ptr.is_null() { + return Err(MemoryError::Allocation(format!("Received a null pointer"))); + } - if let Some(ref cfg) = config { - let actual_distance = (mem_ptr as usize).abs_diff(cfg.last_address); - if actual_distance < cfg.min_distance { - warn!("New allocation distance to previous allocation is below threshold."); - dealloc_direct(mem_ptr)?; - continue; + #[cfg(target_os = "macos")] + { + // on linux it isn't required to commit memory + let error = libc::madvise(c_ptr, actual_size, libc::MADV_WILLNEED); + if error != 0 { + return Err(MemoryError::Allocation(format!( + "memory advise returned an error {}", error; + ))); } } - // we are searching for some address in between - let offset = rng.gen::().min(max - actual_size); - let actual_mem = ((mem_ptr as usize) + offset) as *mut T; - actual_mem.write(T::default()); + c_ptr + }; - return Ok(Frag { - ptr: NonNull::new_unchecked(actual_mem), - strategy: FragStrategy::Direct, - live: true, - info: (mem_ptr, alloc_size), - }); - } + // we are searching for some address in between + let offset = rng.gen::().min(max - actual_size); + let actual_mem = ((mem_ptr as usize) + offset) as *mut T; + actual_mem.write(T::default()); + + return Ok(Frag { + ptr: NonNull::new_unchecked(actual_mem), + strategy: FragStrategy::Direct, + live: true, + info: (mem_ptr, alloc_size), + }); } } @@ -454,41 +428,30 @@ where fn alloc(config: Option) -> Result, Self::Error> { use windows::Win32::System::Memory::{VirtualAlloc, MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE}; - loop { - unsafe { - let actual_size = std::mem::size_of::(); - - let actual_mem = VirtualAlloc( - std::ptr::null_mut(), - actual_size, - MEM_COMMIT | MEM_RESERVE, - PAGE_READWRITE, - ); + unsafe { + let actual_size = std::mem::size_of::(); - if actual_mem.is_null() { - if let Err(_) = last_error() { - continue; - } - } + let actual_mem = VirtualAlloc( + std::ptr::null_mut(), + actual_size, + MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE, + ); - if let Some(ref cfg) = config { - let actual_distance = (actual_mem as usize).abs_diff(cfg.last_address); - if actual_distance < cfg.min_distance { - warn!("New allocation distance to previous allocation is below threshold."); - dealloc_direct(actual_mem)?; - continue; - } + if actual_mem.is_null() { + if let Err(_) = last_error() { + return Err(MemoryError::Allocation(format!("Call to VirtualAlloc failed"))); } - - let actual_mem = actual_mem as *mut T; - actual_mem.write(T::default()); - return Ok(Frag { - ptr: NonNull::new_unchecked(actual_mem), - strategy: FragStrategy::Direct, - live: true, - info: None, - }); } + + let actual_mem = actual_mem as *mut T; + actual_mem.write(T::default()); + return Ok(Frag { + ptr: NonNull::new_unchecked(actual_mem), + strategy: FragStrategy::Direct, + live: true, + info: None, + }); } } From 419e4015cada56e7407bec91bf9406db7f66e502 Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Tue, 12 Jul 2022 18:50:49 +0200 Subject: [PATCH 45/46] Fix syntax error in macos section --- engine/runtime/src/memories/frag.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index 1c50248f8..d7d5d73c7 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -397,7 +397,7 @@ where let error = libc::madvise(c_ptr, actual_size, libc::MADV_WILLNEED); if error != 0 { return Err(MemoryError::Allocation(format!( - "memory advise returned an error {}", error; + "memory advise returned an error {}", error ))); } } From da14dfd7c5f66a071a812ca94dbb4c621c8f38e6 Mon Sep 17 00:00:00 2001 From: Alexandre Dang Date: Tue, 12 Jul 2022 18:56:17 +0200 Subject: [PATCH 46/46] Format with clippy and cargo fmt --- engine/runtime/src/memories/frag.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/engine/runtime/src/memories/frag.rs b/engine/runtime/src/memories/frag.rs index d7d5d73c7..715317b73 100644 --- a/engine/runtime/src/memories/frag.rs +++ b/engine/runtime/src/memories/frag.rs @@ -103,9 +103,7 @@ pub struct FragConfig { impl FragConfig { /// Creates a new [`FragConfig`] pub fn new(last_address: usize) -> Self { - Self { - last_address, - } + Self { last_address } } } @@ -150,7 +148,7 @@ impl Frag { return Ok((a, b)); } - Err(MemoryError::Allocation(format!("Unable to allocate safe fragments"))) + Err(MemoryError::Allocation("Unable to allocate safe fragments".to_string())) } /// Tries to allocate two objects of the same type with a default minimum distance in memory space of @@ -256,7 +254,7 @@ where info!("Preallocated segment addr: {:p}", c_ptr); if c_ptr == libc::MAP_FAILED { - return Err(MemoryError::Allocation(format!("Memory mapping failed"))); + return Err(MemoryError::Allocation("Memory mapping failed".to_string())); } #[cfg(any(target_os = "macos"))] @@ -285,7 +283,7 @@ where info: (c_ptr, desired_alloc_size), }) } else { - Err(MemoryError::Allocation(format!("Received a null pointer"))) + Err(MemoryError::Allocation("Received a null pointer".to_string())) } } } @@ -388,7 +386,7 @@ where // allocate some randomly sized chunk of memory let c_ptr = libc::malloc(alloc_size); if c_ptr.is_null() { - return Err(MemoryError::Allocation(format!("Received a null pointer"))); + return Err(MemoryError::Allocation("Received a null pointer".to_string())); } #[cfg(target_os = "macos")] @@ -397,7 +395,8 @@ where let error = libc::madvise(c_ptr, actual_size, libc::MADV_WILLNEED); if error != 0 { return Err(MemoryError::Allocation(format!( - "memory advise returned an error {}", error + "memory advise returned an error {}", + error ))); } } @@ -410,12 +409,12 @@ where let actual_mem = ((mem_ptr as usize) + offset) as *mut T; actual_mem.write(T::default()); - return Ok(Frag { + Ok(Frag { ptr: NonNull::new_unchecked(actual_mem), strategy: FragStrategy::Direct, live: true, info: (mem_ptr, alloc_size), - }); + }) } }