From ea962f1bdc894a712ca28ec6a08ddf681876fa0c Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Thu, 23 May 2024 19:19:43 +0530 Subject: [PATCH 01/16] (feat): Add memfd secret style allocation --- src/alloc.rs | 166 +++++++++++++++++++++++++++++++++++++++++++-------- src/lib.rs | 23 +++---- 2 files changed, 154 insertions(+), 35 deletions(-) diff --git a/src/alloc.rs b/src/alloc.rs index 2b4f991..4c85737 100644 --- a/src/alloc.rs +++ b/src/alloc.rs @@ -4,14 +4,14 @@ extern crate std; +use self::memfd_secret_alloc::*; +use self::raw_alloc::*; +use self::std::process::abort; +use self::std::sync::Once; use core::mem; -use core::ptr::{ self, NonNull }; +use core::ptr::{self, NonNull}; use core::slice; use getrandom::getrandom; -use self::std::sync::Once; -use self::std::process::abort; -use self::raw_alloc::*; - const GARBAGE_VALUE: u8 = 0xd0; const CANARY_SIZE: usize = 16; @@ -20,16 +20,17 @@ static mut PAGE_SIZE: usize = 0; static mut PAGE_MASK: usize = 0; static mut CANARY: [u8; CANARY_SIZE] = [0; CANARY_SIZE]; - // -- alloc init -- #[inline] unsafe fn alloc_init() { - #[cfg(unix)] { + #[cfg(unix)] + { PAGE_SIZE = libc::sysconf(libc::_SC_PAGESIZE) as usize; } - #[cfg(windows)] { + #[cfg(windows)] + { let mut si = mem::MaybeUninit::uninit(); windows_sys::Win32::System::SystemInformation::GetSystemInfo(si.as_mut_ptr()); PAGE_SIZE = (*si.as_ptr()).dwPageSize as usize; @@ -44,11 +45,10 @@ unsafe fn alloc_init() { getrandom(&mut CANARY).unwrap(); } - // -- aligned alloc / aligned free -- mod raw_alloc { - use super::std::alloc::{ alloc, dealloc, Layout }; + use super::std::alloc::{alloc, dealloc, Layout}; use super::*; #[inline] @@ -64,6 +64,41 @@ mod raw_alloc { } } +#[cfg(target_os = "linux")] +mod memfd_secret_alloc { + use core::convert::TryInto; + + use super::*; + + #[inline] + pub unsafe fn alloc_memfd_secret(size: usize) -> Option> { + let fd: Result = libc::syscall(libc::SYS_memfd_secret, 0).try_into(); + + if fd.is_err() || fd.unwrap() < 0 { + return None; + } + + let fd = fd.unwrap(); + + // File size is set using ftruncate + let _ = libc::ftruncate(fd, size as libc::off_t); + + let ptr = libc::mmap( + ptr::null_mut(), + size, + Prot::ReadWrite, + libc::MAP_SHARED, + fd, + 0, + ); + + if ptr == libc::MAP_FAILED { + return None; + } + + NonNull::new(ptr as *mut u8) + } +} // -- mprotect -- @@ -105,7 +140,6 @@ pub mod Prot { pub const TargetsNoUpdate: Ty = windows_sys::Win32::System::Memory::PAGE_TARGETS_NO_UPDATE; } - /// Unix `mprotect`. #[cfg(unix)] #[inline] @@ -121,7 +155,6 @@ pub unsafe fn _mprotect(ptr: *mut u8, len: usize, prot: Prot::Ty) -> bool { windows_sys::Win32::System::Memory::VirtualProtect(ptr.cast(), len, prot, old.as_mut_ptr()) != 0 } - /// Secure `mprotect`. #[cfg(any(unix, windows))] pub unsafe fn mprotect(memptr: NonNull, prot: Prot::Ty) -> bool { @@ -133,7 +166,6 @@ pub unsafe fn mprotect(memptr: NonNull, prot: Prot::Ty) -> bool { _mprotect(unprotected_ptr, unprotected_size, prot) } - // -- malloc / free -- #[inline] @@ -167,7 +199,11 @@ unsafe fn _malloc(size: usize) -> Option<*mut u8> { // mprotect ptr _mprotect(base_ptr.add(PAGE_SIZE), PAGE_SIZE, Prot::NoAccess); - _mprotect(unprotected_ptr.add(unprotected_size), PAGE_SIZE, Prot::NoAccess); + _mprotect( + unprotected_ptr.add(unprotected_size), + PAGE_SIZE, + Prot::NoAccess, + ); crate::mlock(unprotected_ptr, unprotected_size); let canary_ptr = unprotected_ptr.add(unprotected_size - size_with_canary); @@ -181,24 +217,76 @@ unsafe fn _malloc(size: usize) -> Option<*mut u8> { Some(user_ptr) } +#[cfg(target_os = "linux")] +unsafe fn _memfd_secret(size: usize) -> Option<*mut u8> { + ALLOC_INIT.call_once(|| alloc_init()); + + if size >= ::core::usize::MAX - PAGE_SIZE * 4 { + return None; + } + + // aligned alloc ptr + let size_with_canary = CANARY_SIZE + size; + let unprotected_size = page_round(size_with_canary); + let total_size = PAGE_SIZE + PAGE_SIZE + unprotected_size + PAGE_SIZE; + let base_ptr = alloc_memfd_secret(total_size)?.as_ptr(); + let unprotected_ptr = base_ptr.add(PAGE_SIZE * 2); + + // mprotect can be used to change protection flag after mmap setup + // https://www.gnu.org/software/libc/manual/html_node/Memory-Protection.html#index-mprotect + _mprotect(base_ptr.add(PAGE_SIZE), PAGE_SIZE, Prot::NoAccess); + _mprotect( + unprotected_ptr.add(unprotected_size), + PAGE_SIZE, + Prot::NoAccess, + ); + + let canary_ptr = unprotected_ptr.add(unprotected_size - size_with_canary); + let user_ptr = canary_ptr.add(CANARY_SIZE); + ptr::copy_nonoverlapping(CANARY.as_ptr(), canary_ptr, CANARY_SIZE); + ptr::write_unaligned(base_ptr as *mut usize, unprotected_size); + _mprotect(base_ptr, PAGE_SIZE, Prot::ReadOnly); + + assert_eq!(unprotected_ptr_from_user_ptr(user_ptr), unprotected_ptr); + + Some(user_ptr) +} + /// Secure `malloc`. #[inline] pub unsafe fn malloc() -> Option> { - _malloc(mem::size_of::()) - .map(|memptr| { - ptr::write_bytes(memptr, GARBAGE_VALUE, mem::size_of::()); - NonNull::new_unchecked(memptr as *mut T) - }) + _malloc(mem::size_of::()).map(|memptr| { + ptr::write_bytes(memptr, GARBAGE_VALUE, mem::size_of::()); + NonNull::new_unchecked(memptr as *mut T) + }) } /// Secure `malloc_sized`. #[inline] pub unsafe fn malloc_sized(size: usize) -> Option> { - _malloc(size) - .map(|memptr| { - ptr::write_bytes(memptr, GARBAGE_VALUE, size); - NonNull::new_unchecked(slice::from_raw_parts_mut(memptr, size)) - }) + _malloc(size).map(|memptr| { + ptr::write_bytes(memptr, GARBAGE_VALUE, size); + NonNull::new_unchecked(slice::from_raw_parts_mut(memptr, size)) + }) +} + +#[inline] +#[cfg(target_os = "linux")] +pub unsafe fn memfd_secret() -> Option> { + _memfd_secret(mem::size_of::()).map(|memptr| { + ptr::write_bytes(memptr, GARBAGE_VALUE, mem::size_of::()); + NonNull::new_unchecked(memptr as *mut T) + }) +} + +/// Secure `malloc_sized`. +#[inline] +#[cfg(target_os = "linux")] +pub unsafe fn memfd_secret_sized(size: usize) -> Option> { + _memfd_secret(size).map(|memptr| { + ptr::write_bytes(memptr, GARBAGE_VALUE, size); + NonNull::new_unchecked(slice::from_raw_parts_mut(memptr, size)) + }) } /// Secure `free`. @@ -224,3 +312,33 @@ pub unsafe fn free(memptr: NonNull) { free_aligned(base_ptr, total_size); } + +/// Secure `free` for memfd_secret, +/// i.e. provides read write access back to mprotect guard pages +/// and unmaps mmap +#[cfg(target_os = "linux")] +pub unsafe fn free_memfd_secret(memptr: NonNull) { + use libc::c_void; + + let memptr = memptr.as_ptr() as *mut u8; + + // get unprotected ptr + let canary_ptr = memptr.sub(CANARY_SIZE); + let unprotected_ptr = unprotected_ptr_from_user_ptr(memptr); + let base_ptr = unprotected_ptr.sub(PAGE_SIZE * 2); + let unprotected_size = ptr::read(base_ptr as *const usize); + + // check + if !crate::memeq(canary_ptr as *const u8, CANARY.as_ptr(), CANARY_SIZE) { + abort(); + } + + // free + let total_size = PAGE_SIZE + PAGE_SIZE + unprotected_size + PAGE_SIZE; + _mprotect(base_ptr, total_size, Prot::ReadWrite); + + let res = libc::munmap(base_ptr as *mut c_void, total_size); + if res < 0 { + abort(); + } +} diff --git a/src/lib.rs b/src/lib.rs index a1814d0..48b19e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,20 +1,21 @@ #![no_std] - #![cfg_attr(feature = "nightly", feature(core_intrinsics))] #![allow(clippy::missing_safety_doc)] -mod mlock; mod alloc; +mod mlock; use core::ptr; #[cfg(feature = "use_os")] -pub use mlock::{ mlock, munlock }; +pub use mlock::{mlock, munlock}; #[cfg(feature = "alloc")] -pub use alloc::{ Prot, mprotect, malloc, malloc_sized, free }; - +pub use alloc::{free, malloc, malloc_sized, mprotect, Prot}; +#[cfg(feature = "alloc")] +#[cfg(target_os = "linux")] +pub use alloc::{free_memfd_secret, memfd_secret, memfd_secret_sized}; // -- memcmp -- /// Secure `memeq`. @@ -26,30 +27,30 @@ pub unsafe fn memeq(b1: *const u8, b2: *const u8, len: usize) -> bool { .eq(&0) } - /// Secure `memcmp`. #[inline(never)] pub unsafe fn memcmp(b1: *const u8, b2: *const u8, len: usize) -> i32 { let mut res = 0; for i in (0..len).rev() { - let diff = i32::from(ptr::read_volatile(b1.add(i))) - - i32::from(ptr::read_volatile(b2.add(i))); + let diff = + i32::from(ptr::read_volatile(b1.add(i))) - i32::from(ptr::read_volatile(b2.add(i))); res = (res & (((diff - 1) & !diff) >> 8)) | diff; } ((res - 1) >> 8) + (res >> 8) + 1 } - // -- memset / memzero -- /// General `memset`. #[inline(never)] pub unsafe fn memset(s: *mut u8, c: u8, n: usize) { - #[cfg(feature = "nightly")] { + #[cfg(feature = "nightly")] + { core::intrinsics::volatile_set_memory(s, c, n); } - #[cfg(not(feature = "nightly"))] { + #[cfg(not(feature = "nightly"))] + { let s = ptr::read_volatile(&s); let c = ptr::read_volatile(&c); let n = ptr::read_volatile(&n); From 9824417a74a8e6e2a00ae05d88a8e683e22c7c9c Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Thu, 23 May 2024 19:58:04 +0530 Subject: [PATCH 02/16] Fix error on compilation on non-linux targets --- src/alloc.rs | 2 ++ src/mlock.rs | 13 ++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/alloc.rs b/src/alloc.rs index 4c85737..dc8367c 100644 --- a/src/alloc.rs +++ b/src/alloc.rs @@ -4,7 +4,9 @@ extern crate std; +#[cfg(target_os = "linux")] use self::memfd_secret_alloc::*; + use self::raw_alloc::*; use self::std::process::abort; use self::std::sync::Once; diff --git a/src/mlock.rs b/src/mlock.rs index 41a5e9d..f85d171 100644 --- a/src/mlock.rs +++ b/src/mlock.rs @@ -2,13 +2,13 @@ #![cfg(feature = "use_os")] - /// Cross-platform `mlock`. /// /// * Unix `mlock`. /// * Windows `VirtualLock`. pub unsafe fn mlock(addr: *mut u8, len: usize) -> bool { - #[cfg(unix)] { + #[cfg(unix)] + { #[cfg(target_os = "linux")] libc::madvise(addr as *mut libc::c_void, len, libc::MADV_DONTDUMP); @@ -18,7 +18,8 @@ pub unsafe fn mlock(addr: *mut u8, len: usize) -> bool { libc::mlock(addr as *mut libc::c_void, len) == 0 } - #[cfg(windows)] { + #[cfg(windows)] + { windows_sys::Win32::System::Memory::VirtualLock(addr.cast(), len) != 0 } } @@ -30,7 +31,8 @@ pub unsafe fn mlock(addr: *mut u8, len: usize) -> bool { pub unsafe fn munlock(addr: *mut u8, len: usize) -> bool { crate::memzero(addr, len); - #[cfg(unix)] { + #[cfg(unix)] + { #[cfg(target_os = "linux")] libc::madvise(addr as *mut libc::c_void, len, libc::MADV_DODUMP); @@ -40,7 +42,8 @@ pub unsafe fn munlock(addr: *mut u8, len: usize) -> bool { libc::munlock(addr as *mut libc::c_void, len) == 0 } - #[cfg(windows)] { + #[cfg(windows)] + { windows_sys::Win32::System::Memory::VirtualUnlock(addr.cast(), len) != 0 } } From 38e1d6b450a923f75e01829174dc64a2d4a7c68e Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Fri, 24 May 2024 14:58:10 +0530 Subject: [PATCH 03/16] Review changes: move to another file, add tests, add behind feature --- Cargo.toml | 19 ++-- memsec-test/Cargo.toml | 3 +- memsec-test/tests/allocext_linux.rs | 86 +++++++++++++++++ src/alloc/allocext.rs | 138 ++++++++++++++++++++++++++++ src/{alloc.rs => alloc/mod.rs} | 126 +------------------------ src/lib.rs | 5 +- 6 files changed, 241 insertions(+), 136 deletions(-) create mode 100644 memsec-test/tests/allocext_linux.rs create mode 100644 src/alloc/allocext.rs rename src/{alloc.rs => alloc/mod.rs} (66%) diff --git a/Cargo.toml b/Cargo.toml index 4656c29..7987aea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,10 @@ version = "0.6.3" authors = ["quininer kel "] description = "Rust implementation `libsodium/utils`." repository = "https://github.com/quininer/memsec" -keywords = [ "protection", "memory", "secure" ] +keywords = ["protection", "memory", "secure"] documentation = "https://docs.rs/memsec/" license = "MIT" -categories = [ "no-std", "memory-management" ] +categories = ["no-std", "memory-management"] edition = "2018" [badges] @@ -22,14 +22,15 @@ libc = { version = "0.2", optional = true } [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.45", default-features = false, features = [ - "Win32_System_SystemInformation", - "Win32_System_Memory", - "Win32_Foundation", - "Win32_System_Diagnostics_Debug" + "Win32_System_SystemInformation", + "Win32_System_Memory", + "Win32_Foundation", + "Win32_System_Diagnostics_Debug", ], optional = true } [features] -default = [ "use_os", "alloc" ] +default = ["use_os", "alloc"] nightly = [] -use_os = [ "libc", "windows-sys" ] -alloc = [ "getrandom", "use_os" ] +use_os = ["libc", "windows-sys"] +alloc = ["getrandom", "use_os"] +alloc_ext = ["alloc"] diff --git a/memsec-test/Cargo.toml b/memsec-test/Cargo.toml index b97b9a4..a5a2956 100644 --- a/memsec-test/Cargo.toml +++ b/memsec-test/Cargo.toml @@ -18,7 +18,8 @@ libsodium-sys = { version = "0.2" } nix = "0.26" [features] -default = [ "alloc", "use_os" ] +default = [ "alloc", "use_os", "alloc_ext"] nightly = [ "memsec/nightly" ] use_os = [ "memsec/use_os" ] alloc = [ "memsec/alloc" ] +alloc_ext = [ "memsec/alloc_ext" ] diff --git a/memsec-test/tests/allocext_linux.rs b/memsec-test/tests/allocext_linux.rs new file mode 100644 index 0000000..c434340 --- /dev/null +++ b/memsec-test/tests/allocext_linux.rs @@ -0,0 +1,86 @@ +#![cfg(feature = "alloc_ext")] +#![cfg(target_os = "linux")] + +use std::ptr::NonNull; + + +#[test] +fn memfd_secret_u64_test() { + unsafe { + let mut p: NonNull = memsec::memfd_secret().unwrap(); + *p.as_mut() = std::u64::MAX; + assert_eq!(*p.as_ref(), std::u64::MAX); + memsec::free_memfd_secret(p); + } +} + +#[test] +fn memfd_secret_free_test() { + unsafe { + let memptr: Option> = memsec::memfd_secret(); + assert!(memptr.is_some()); + if let Some(memptr) = memptr { + memsec::free_memfd_secret(memptr); + } + + let memptr: Option> = memsec::memfd_secret(); + assert!(memptr.is_some()); + if let Some(memptr) = memptr { + memsec::free_memfd_secret(memptr); + } + + let memptr: Option> = memsec::memfd_secret_sized(1024); + assert!(memptr.is_some()); + if let Some(memptr) = memptr { + memsec::free_memfd_secret(memptr); + } + + // let memptr: Option> = memsec::memfd_secret(); + // assert!(memptr.is_none()); + } +} + +#[test] +fn memfd_secret_mprotect_1_test() { + unsafe { + let mut x: NonNull<[u8; 16]> = memsec::memfd_secret().unwrap(); + + memsec::memset(x.as_mut().as_mut_ptr(), 0x01, 16); + assert!(memsec::mprotect(x, memsec::Prot::ReadOnly)); + assert!(memsec::memeq(x.as_ref().as_ptr(), [1; 16].as_ptr(), 16)); + assert!(memsec::mprotect(x, memsec::Prot::NoAccess)); + assert!(memsec::mprotect(x, memsec::Prot::ReadWrite)); + memsec::memzero(x.as_mut().as_mut_ptr(), 16); + memsec::free_memfd_secret(x); + } + + unsafe { + let mut x: NonNull<[u8; 4096]> = memsec::memfd_secret().unwrap(); + memsec::memset(x.as_mut().as_mut_ptr(), 0x02, 96); + memsec::free_memfd_secret(x); + } + + unsafe { + let mut x: NonNull<[u8; 4100]> = memsec::memfd_secret().unwrap(); + memsec::memset(x.as_mut().as_mut_ptr().offset(100), 0x03, 3000); + memsec::free_memfd_secret(x); + } + + unsafe { + let mut x = memsec::memfd_secret_sized(16).unwrap(); + + memsec::memset(x.as_mut().as_mut_ptr(), 0x01, 16); + assert!(memsec::mprotect(x, memsec::Prot::ReadOnly)); + assert!(memsec::memeq(x.as_ref().as_ptr(), [1; 16].as_ptr(), 16)); + assert!(memsec::mprotect(x, memsec::Prot::NoAccess)); + assert!(memsec::mprotect(x, memsec::Prot::ReadWrite)); + memsec::memzero(x.as_mut().as_mut_ptr(), 16); + memsec::free_memfd_secret(x); + } + + unsafe { + let mut x = memsec::memfd_secret_sized(4100).unwrap(); + memsec::memset(x.as_mut().as_mut_ptr().offset(100), 0x03, 3000); + memsec::free_memfd_secret(x); + } +} diff --git a/src/alloc/allocext.rs b/src/alloc/allocext.rs new file mode 100644 index 0000000..adc9e40 --- /dev/null +++ b/src/alloc/allocext.rs @@ -0,0 +1,138 @@ +//! allocext +//! OS Specific allocation +//! +//! + +#![cfg(feature = "alloc_ext")] +extern crate std; +use self::std::process::abort; +use crate::{alloc::*, Prot }; +use core::mem; +use core::ptr::{self, NonNull}; +use core::slice; + +#[cfg(target_os = "linux")] +use self::memfd_secret_alloc::*; + +#[cfg(target_os = "linux")] +mod memfd_secret_alloc { + use core::convert::TryInto; + + use super::*; + + #[inline] + pub unsafe fn alloc_memfd_secret(size: usize) -> Option> { + let fd: Result = libc::syscall(libc::SYS_memfd_secret, 0).try_into(); + + if fd.is_err() || fd.unwrap() < 0 { + return None; + } + + let Ok(fd) = fd else { + return None; + }; + + // File size is set using ftruncate + let _ = libc::ftruncate(fd, size as libc::off_t); + + let ptr = libc::mmap( + ptr::null_mut(), + size, + Prot::ReadWrite, + libc::MAP_SHARED, + fd, + 0, + ); + + if ptr == libc::MAP_FAILED { + return None; + } + + NonNull::new(ptr as *mut u8) + } +} + +#[cfg(target_os = "linux")] +unsafe fn _memfd_secret(size: usize) -> Option<*mut u8> { + ALLOC_INIT.call_once(|| alloc_init()); + + if size >= ::core::usize::MAX - PAGE_SIZE * 4 { + return None; + } + + // aligned alloc ptr + let size_with_canary = CANARY_SIZE + size; + let unprotected_size = page_round(size_with_canary); + let total_size = PAGE_SIZE + PAGE_SIZE + unprotected_size + PAGE_SIZE; + let base_ptr = alloc_memfd_secret(total_size)?.as_ptr(); + let unprotected_ptr = base_ptr.add(PAGE_SIZE * 2); + + // mprotect can be used to change protection flag after mmap setup + // https://www.gnu.org/software/libc/manual/html_node/Memory-Protection.html#index-mprotect + _mprotect(base_ptr.add(PAGE_SIZE), PAGE_SIZE, Prot::NoAccess); + _mprotect( + unprotected_ptr.add(unprotected_size), + PAGE_SIZE, + Prot::NoAccess, + ); + + let canary_ptr = unprotected_ptr.add(unprotected_size - size_with_canary); + let user_ptr = canary_ptr.add(CANARY_SIZE); + ptr::copy_nonoverlapping(CANARY.as_ptr(), canary_ptr, CANARY_SIZE); + ptr::write_unaligned(base_ptr as *mut usize, unprotected_size); + _mprotect(base_ptr, PAGE_SIZE, Prot::ReadOnly); + + assert_eq!(unprotected_ptr_from_user_ptr(user_ptr), unprotected_ptr); + + Some(user_ptr) +} + +/// Linux specific `memfd_secret` backed allocation +#[inline] +#[cfg(target_os = "linux")] +pub unsafe fn memfd_secret() -> Option> { + _memfd_secret(mem::size_of::()).map(|memptr| { + ptr::write_bytes(memptr, GARBAGE_VALUE, mem::size_of::()); + NonNull::new_unchecked(memptr as *mut T) + }) +} + +/// Linux specific `memfd_secret` backed `sized` allocation +#[inline] +#[cfg(target_os = "linux")] +pub unsafe fn memfd_secret_sized(size: usize) -> Option> { + _memfd_secret(size).map(|memptr| { + ptr::write_bytes(memptr, GARBAGE_VALUE, size); + NonNull::new_unchecked(slice::from_raw_parts_mut(memptr, size)) + }) +} + +/// Secure `free` for memfd_secret allocations, +/// i.e. provides read write access back to mprotect guard pages +/// and unmaps mmaped secrets +#[cfg(target_os = "linux")] +pub unsafe fn free_memfd_secret(memptr: NonNull) { + use libc::c_void; + + let memptr = memptr.as_ptr() as *mut u8; + + // get unprotected ptr + let canary_ptr = memptr.sub(CANARY_SIZE); + let unprotected_ptr = unprotected_ptr_from_user_ptr(memptr); + let base_ptr = unprotected_ptr.sub(PAGE_SIZE * 2); + let unprotected_size = ptr::read(base_ptr as *const usize); + + // check + if !crate::memeq(canary_ptr as *const u8, CANARY.as_ptr(), CANARY_SIZE) { + abort(); + } + + // free + let total_size = PAGE_SIZE + PAGE_SIZE + unprotected_size + PAGE_SIZE; + _mprotect(base_ptr, total_size, Prot::ReadWrite); + + let res = libc::munmap(base_ptr as *mut c_void, total_size); + if res < 0 { + abort(); + } +} diff --git a/src/alloc.rs b/src/alloc/mod.rs similarity index 66% rename from src/alloc.rs rename to src/alloc/mod.rs index dc8367c..40e65c0 100644 --- a/src/alloc.rs +++ b/src/alloc/mod.rs @@ -2,11 +2,9 @@ #![cfg(feature = "alloc")] -extern crate std; - -#[cfg(target_os = "linux")] -use self::memfd_secret_alloc::*; +pub mod allocext; +extern crate std; use self::raw_alloc::*; use self::std::process::abort; use self::std::sync::Once; @@ -66,42 +64,6 @@ mod raw_alloc { } } -#[cfg(target_os = "linux")] -mod memfd_secret_alloc { - use core::convert::TryInto; - - use super::*; - - #[inline] - pub unsafe fn alloc_memfd_secret(size: usize) -> Option> { - let fd: Result = libc::syscall(libc::SYS_memfd_secret, 0).try_into(); - - if fd.is_err() || fd.unwrap() < 0 { - return None; - } - - let fd = fd.unwrap(); - - // File size is set using ftruncate - let _ = libc::ftruncate(fd, size as libc::off_t); - - let ptr = libc::mmap( - ptr::null_mut(), - size, - Prot::ReadWrite, - libc::MAP_SHARED, - fd, - 0, - ); - - if ptr == libc::MAP_FAILED { - return None; - } - - NonNull::new(ptr as *mut u8) - } -} - // -- mprotect -- /// Prot enum. @@ -219,41 +181,6 @@ unsafe fn _malloc(size: usize) -> Option<*mut u8> { Some(user_ptr) } -#[cfg(target_os = "linux")] -unsafe fn _memfd_secret(size: usize) -> Option<*mut u8> { - ALLOC_INIT.call_once(|| alloc_init()); - - if size >= ::core::usize::MAX - PAGE_SIZE * 4 { - return None; - } - - // aligned alloc ptr - let size_with_canary = CANARY_SIZE + size; - let unprotected_size = page_round(size_with_canary); - let total_size = PAGE_SIZE + PAGE_SIZE + unprotected_size + PAGE_SIZE; - let base_ptr = alloc_memfd_secret(total_size)?.as_ptr(); - let unprotected_ptr = base_ptr.add(PAGE_SIZE * 2); - - // mprotect can be used to change protection flag after mmap setup - // https://www.gnu.org/software/libc/manual/html_node/Memory-Protection.html#index-mprotect - _mprotect(base_ptr.add(PAGE_SIZE), PAGE_SIZE, Prot::NoAccess); - _mprotect( - unprotected_ptr.add(unprotected_size), - PAGE_SIZE, - Prot::NoAccess, - ); - - let canary_ptr = unprotected_ptr.add(unprotected_size - size_with_canary); - let user_ptr = canary_ptr.add(CANARY_SIZE); - ptr::copy_nonoverlapping(CANARY.as_ptr(), canary_ptr, CANARY_SIZE); - ptr::write_unaligned(base_ptr as *mut usize, unprotected_size); - _mprotect(base_ptr, PAGE_SIZE, Prot::ReadOnly); - - assert_eq!(unprotected_ptr_from_user_ptr(user_ptr), unprotected_ptr); - - Some(user_ptr) -} - /// Secure `malloc`. #[inline] pub unsafe fn malloc() -> Option> { @@ -272,25 +199,6 @@ pub unsafe fn malloc_sized(size: usize) -> Option> { }) } -#[inline] -#[cfg(target_os = "linux")] -pub unsafe fn memfd_secret() -> Option> { - _memfd_secret(mem::size_of::()).map(|memptr| { - ptr::write_bytes(memptr, GARBAGE_VALUE, mem::size_of::()); - NonNull::new_unchecked(memptr as *mut T) - }) -} - -/// Secure `malloc_sized`. -#[inline] -#[cfg(target_os = "linux")] -pub unsafe fn memfd_secret_sized(size: usize) -> Option> { - _memfd_secret(size).map(|memptr| { - ptr::write_bytes(memptr, GARBAGE_VALUE, size); - NonNull::new_unchecked(slice::from_raw_parts_mut(memptr, size)) - }) -} - /// Secure `free`. pub unsafe fn free(memptr: NonNull) { let memptr = memptr.as_ptr() as *mut u8; @@ -314,33 +222,3 @@ pub unsafe fn free(memptr: NonNull) { free_aligned(base_ptr, total_size); } - -/// Secure `free` for memfd_secret, -/// i.e. provides read write access back to mprotect guard pages -/// and unmaps mmap -#[cfg(target_os = "linux")] -pub unsafe fn free_memfd_secret(memptr: NonNull) { - use libc::c_void; - - let memptr = memptr.as_ptr() as *mut u8; - - // get unprotected ptr - let canary_ptr = memptr.sub(CANARY_SIZE); - let unprotected_ptr = unprotected_ptr_from_user_ptr(memptr); - let base_ptr = unprotected_ptr.sub(PAGE_SIZE * 2); - let unprotected_size = ptr::read(base_ptr as *const usize); - - // check - if !crate::memeq(canary_ptr as *const u8, CANARY.as_ptr(), CANARY_SIZE) { - abort(); - } - - // free - let total_size = PAGE_SIZE + PAGE_SIZE + unprotected_size + PAGE_SIZE; - _mprotect(base_ptr, total_size, Prot::ReadWrite); - - let res = libc::munmap(base_ptr as *mut c_void, total_size); - if res < 0 { - abort(); - } -} diff --git a/src/lib.rs b/src/lib.rs index 48b19e5..de0526c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,9 +13,10 @@ pub use mlock::{mlock, munlock}; #[cfg(feature = "alloc")] pub use alloc::{free, malloc, malloc_sized, mprotect, Prot}; -#[cfg(feature = "alloc")] +#[cfg(feature = "alloc_ext")] #[cfg(target_os = "linux")] -pub use alloc::{free_memfd_secret, memfd_secret, memfd_secret_sized}; +pub use alloc::allocext::{free_memfd_secret, memfd_secret, memfd_secret_sized}; + // -- memcmp -- /// Secure `memeq`. From abd3d9923ced03ca476c33d9cc8684c79366fabe Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Mon, 27 May 2024 14:52:04 +0530 Subject: [PATCH 04/16] Review changes: alloc_ext deps in test, reorganize allocext --- memsec-test/Cargo.toml | 2 +- src/alloc/{allocext.rs => allocext/linux.rs} | 14 +------------- src/alloc/allocext/mod.rs | 11 +++++++++++ 3 files changed, 13 insertions(+), 14 deletions(-) rename src/alloc/{allocext.rs => allocext/linux.rs} (93%) create mode 100644 src/alloc/allocext/mod.rs diff --git a/memsec-test/Cargo.toml b/memsec-test/Cargo.toml index a5a2956..b9721fe 100644 --- a/memsec-test/Cargo.toml +++ b/memsec-test/Cargo.toml @@ -22,4 +22,4 @@ default = [ "alloc", "use_os", "alloc_ext"] nightly = [ "memsec/nightly" ] use_os = [ "memsec/use_os" ] alloc = [ "memsec/alloc" ] -alloc_ext = [ "memsec/alloc_ext" ] +alloc_ext = [ "memsec/alloc_ext", "use_os" ] diff --git a/src/alloc/allocext.rs b/src/alloc/allocext/linux.rs similarity index 93% rename from src/alloc/allocext.rs rename to src/alloc/allocext/linux.rs index adc9e40..68187f7 100644 --- a/src/alloc/allocext.rs +++ b/src/alloc/allocext/linux.rs @@ -1,20 +1,12 @@ -//! allocext -//! OS Specific allocation -//! -//! - -#![cfg(feature = "alloc_ext")] extern crate std; use self::std::process::abort; -use crate::{alloc::*, Prot }; +use crate::{alloc::*, Prot}; use core::mem; use core::ptr::{self, NonNull}; use core::slice; -#[cfg(target_os = "linux")] use self::memfd_secret_alloc::*; -#[cfg(target_os = "linux")] mod memfd_secret_alloc { use core::convert::TryInto; @@ -52,7 +44,6 @@ mod memfd_secret_alloc { } } -#[cfg(target_os = "linux")] unsafe fn _memfd_secret(size: usize) -> Option<*mut u8> { ALLOC_INIT.call_once(|| alloc_init()); @@ -89,7 +80,6 @@ unsafe fn _memfd_secret(size: usize) -> Option<*mut u8> { /// Linux specific `memfd_secret` backed allocation #[inline] -#[cfg(target_os = "linux")] pub unsafe fn memfd_secret() -> Option> { _memfd_secret(mem::size_of::()).map(|memptr| { ptr::write_bytes(memptr, GARBAGE_VALUE, mem::size_of::()); @@ -99,7 +89,6 @@ pub unsafe fn memfd_secret() -> Option> { /// Linux specific `memfd_secret` backed `sized` allocation #[inline] -#[cfg(target_os = "linux")] pub unsafe fn memfd_secret_sized(size: usize) -> Option> { _memfd_secret(size).map(|memptr| { ptr::write_bytes(memptr, GARBAGE_VALUE, size); @@ -110,7 +99,6 @@ pub unsafe fn memfd_secret_sized(size: usize) -> Option> { /// Secure `free` for memfd_secret allocations, /// i.e. provides read write access back to mprotect guard pages /// and unmaps mmaped secrets -#[cfg(target_os = "linux")] pub unsafe fn free_memfd_secret(memptr: NonNull) { use libc::c_void; diff --git a/src/alloc/allocext/mod.rs b/src/alloc/allocext/mod.rs new file mode 100644 index 0000000..27b1b00 --- /dev/null +++ b/src/alloc/allocext/mod.rs @@ -0,0 +1,11 @@ +//! allocext +//! OS Specific allocation +//! +//! +#![cfg(feature = "alloc_ext")] + +#[cfg(target_os = "linux")] +mod linux; + +#[cfg(target_os = "linux")] +pub use self::linux::*; From d675c19ea511e993afaf70ef64aff3c9dcbb2fd5 Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Mon, 27 May 2024 17:26:08 +0530 Subject: [PATCH 05/16] Apply review suggestion --- src/alloc/allocext/linux.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/alloc/allocext/linux.rs b/src/alloc/allocext/linux.rs index 68187f7..d3ce465 100644 --- a/src/alloc/allocext/linux.rs +++ b/src/alloc/allocext/linux.rs @@ -16,13 +16,7 @@ mod memfd_secret_alloc { pub unsafe fn alloc_memfd_secret(size: usize) -> Option> { let fd: Result = libc::syscall(libc::SYS_memfd_secret, 0).try_into(); - if fd.is_err() || fd.unwrap() < 0 { - return None; - } - - let Ok(fd) = fd else { - return None; - }; + let fd = fd.ok().filter(|&fd| fd >= 0)?; // File size is set using ftruncate let _ = libc::ftruncate(fd, size as libc::off_t); From 3a1646ca18225171bc74f36f3b7bc51ac03a345a Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Tue, 28 May 2024 21:22:17 +0530 Subject: [PATCH 06/16] Fuzz test findings- close fd when freeing --- src/alloc/allocext/linux.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/alloc/allocext/linux.rs b/src/alloc/allocext/linux.rs index d3ce465..46315fe 100644 --- a/src/alloc/allocext/linux.rs +++ b/src/alloc/allocext/linux.rs @@ -1,7 +1,7 @@ extern crate std; use self::std::process::abort; use crate::{alloc::*, Prot}; -use core::mem; +use core::mem::{self, size_of}; use core::ptr::{self, NonNull}; use core::slice; @@ -9,11 +9,12 @@ use self::memfd_secret_alloc::*; mod memfd_secret_alloc { use core::convert::TryInto; + use std::{io, println}; use super::*; #[inline] - pub unsafe fn alloc_memfd_secret(size: usize) -> Option> { + pub unsafe fn alloc_memfd_secret(size: usize) -> Option<(NonNull, libc::c_int)> { let fd: Result = libc::syscall(libc::SYS_memfd_secret, 0).try_into(); let fd = fd.ok().filter(|&fd| fd >= 0)?; @@ -34,7 +35,7 @@ mod memfd_secret_alloc { return None; } - NonNull::new(ptr as *mut u8) + NonNull::new(ptr as *mut u8).map(|ptr| (ptr, fd)) } } @@ -49,7 +50,9 @@ unsafe fn _memfd_secret(size: usize) -> Option<*mut u8> { let size_with_canary = CANARY_SIZE + size; let unprotected_size = page_round(size_with_canary); let total_size = PAGE_SIZE + PAGE_SIZE + unprotected_size + PAGE_SIZE; - let base_ptr = alloc_memfd_secret(total_size)?.as_ptr(); + let (base_ptr, fd) = alloc_memfd_secret(total_size)?; + let base_ptr = base_ptr.as_ptr(); + let fd_ptr = base_ptr.add(size_of::()); let unprotected_ptr = base_ptr.add(PAGE_SIZE * 2); // mprotect can be used to change protection flag after mmap setup @@ -65,6 +68,7 @@ unsafe fn _memfd_secret(size: usize) -> Option<*mut u8> { let user_ptr = canary_ptr.add(CANARY_SIZE); ptr::copy_nonoverlapping(CANARY.as_ptr(), canary_ptr, CANARY_SIZE); ptr::write_unaligned(base_ptr as *mut usize, unprotected_size); + ptr::write_unaligned(fd_ptr as *mut libc::c_int, fd); _mprotect(base_ptr, PAGE_SIZE, Prot::ReadOnly); assert_eq!(unprotected_ptr_from_user_ptr(user_ptr), unprotected_ptr); @@ -102,7 +106,9 @@ pub unsafe fn free_memfd_secret(memptr: NonNull) { let canary_ptr = memptr.sub(CANARY_SIZE); let unprotected_ptr = unprotected_ptr_from_user_ptr(memptr); let base_ptr = unprotected_ptr.sub(PAGE_SIZE * 2); + let fd_ptr = base_ptr.add(size_of::()) as *mut libc::c_int; let unprotected_size = ptr::read(base_ptr as *const usize); + let fd = ptr::read(fd_ptr); // check if !crate::memeq(canary_ptr as *const u8, CANARY.as_ptr(), CANARY_SIZE) { @@ -117,4 +123,9 @@ pub unsafe fn free_memfd_secret(memptr: NonNull) { if res < 0 { abort(); } + + let res = libc::close(fd); + if res < 0 { + abort(); + } } From 6b33cbef29e1b198bf779f41c48c36c25a991cba Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Thu, 30 May 2024 15:48:42 +0530 Subject: [PATCH 07/16] Sanity check tests: add tests to probe changes in guard_pages, canary --- memsec-test/Cargo.toml | 3 + memsec-test/benches/malloc.rs | 1 - memsec-test/benches/memcmp.rs | 57 +++++---- memsec-test/benches/memzero.rs | 10 +- memsec-test/tests/allocext_linux.rs | 185 +++++++++++++++++++++++++++- memsec-test/tests/malloc.rs | 184 ++++++++++++++++++++++++++- memsec-test/tests/tests.rs | 24 ++-- 7 files changed, 418 insertions(+), 46 deletions(-) diff --git a/memsec-test/Cargo.toml b/memsec-test/Cargo.toml index b9721fe..14489c1 100644 --- a/memsec-test/Cargo.toml +++ b/memsec-test/Cargo.toml @@ -12,10 +12,13 @@ default-features = false [dev-dependencies] libc = "0.2" quickcheck = "1" +procspawn = {version = "1.0.0", features = ["test-support"]} [target.'cfg(unix)'.dev-dependencies] libsodium-sys = { version = "0.2" } nix = "0.26" +ipc-channel = "0.18.0" +serde = "1.0.203" [features] default = [ "alloc", "use_os", "alloc_ext"] diff --git a/memsec-test/benches/malloc.rs b/memsec-test/benches/malloc.rs index 0100146..812fac9 100644 --- a/memsec-test/benches/malloc.rs +++ b/memsec-test/benches/malloc.rs @@ -6,7 +6,6 @@ extern crate test; use std::ptr::NonNull; use test::Bencher; - #[bench] fn memsec_malloc(b: &mut Bencher) { b.iter(|| unsafe { diff --git a/memsec-test/benches/memcmp.rs b/memsec-test/benches/memcmp.rs index 9094dba..0f88e18 100644 --- a/memsec-test/benches/memcmp.rs +++ b/memsec-test/benches/memcmp.rs @@ -2,19 +2,16 @@ extern crate test; -use test::Bencher; -use std::mem::size_of_val; use libc::c_void; - +use std::mem::size_of_val; +use test::Bencher; #[bench] fn memsec_memeq_eq_bench(b: &mut Bencher) { let x: [u8; 1025] = [9; 1025]; let y: [u8; 1025] = [9; 1025]; - b.iter(|| unsafe { - memsec::memeq(x.as_ptr(), y.as_ptr(), size_of_val(&y)) - }); + b.iter(|| unsafe { memsec::memeq(x.as_ptr(), y.as_ptr(), size_of_val(&y)) }); } #[bench] @@ -22,9 +19,7 @@ fn memsec_memeq_nq_bench(b: &mut Bencher) { let x: [u8; 1025] = [8; 1025]; let z: [u8; 1025] = [3; 1025]; - b.iter(|| unsafe { - memsec::memeq(x.as_ptr(), z.as_ptr(), size_of_val(&z)) - }); + b.iter(|| unsafe { memsec::memeq(x.as_ptr(), z.as_ptr(), size_of_val(&z)) }); } #[bench] @@ -32,9 +27,7 @@ fn memsec_memcmp_eq_bench(b: &mut Bencher) { let x: [u8; 1025] = [9; 1025]; let y: [u8; 1025] = [9; 1025]; - b.iter(|| unsafe { - memsec::memcmp(x.as_ptr(), y.as_ptr(), size_of_val(&y)) - }); + b.iter(|| unsafe { memsec::memcmp(x.as_ptr(), y.as_ptr(), size_of_val(&y)) }); } #[bench] @@ -42,9 +35,7 @@ fn memsec_memcmp_nq_bench(b: &mut Bencher) { let x: [u8; 1025] = [8; 1025]; let z: [u8; 1025] = [3; 1025]; - b.iter(|| unsafe { - memsec::memcmp(x.as_ptr(), z.as_ptr(), size_of_val(&z)) - }); + b.iter(|| unsafe { memsec::memcmp(x.as_ptr(), z.as_ptr(), size_of_val(&z)) }); } #[cfg(unix)] @@ -54,7 +45,11 @@ fn libsodium_memcmp_eq_bench(b: &mut Bencher) { let y: [u8; 1025] = [9; 1025]; b.iter(|| unsafe { - libsodium_sys::sodium_memcmp(x.as_ptr() as *const _, y.as_ptr() as *const _, size_of_val(&y)) + libsodium_sys::sodium_memcmp( + x.as_ptr() as *const _, + y.as_ptr() as *const _, + size_of_val(&y), + ) }); } @@ -65,7 +60,11 @@ fn libsodium_memcmp_nq_bench(b: &mut Bencher) { let z: [u8; 1025] = [3; 1025]; b.iter(|| unsafe { - libsodium_sys::sodium_memcmp(x.as_ptr() as *const _, z.as_ptr() as *const _, size_of_val(&z)) + libsodium_sys::sodium_memcmp( + x.as_ptr() as *const _, + z.as_ptr() as *const _, + size_of_val(&z), + ) }); } @@ -76,7 +75,11 @@ fn libsodium_compare_nq_bench(b: &mut Bencher) { let z: [u8; 1025] = [3; 1025]; b.iter(|| unsafe { - libsodium_sys::sodium_compare(x.as_ptr() as *const _, z.as_ptr() as *const _, size_of_val(&z)) + libsodium_sys::sodium_compare( + x.as_ptr() as *const _, + z.as_ptr() as *const _, + size_of_val(&z), + ) }); } @@ -87,7 +90,11 @@ fn libsodium_compare_eq_bench(b: &mut Bencher) { let y: [u8; 1025] = [9; 1025]; b.iter(|| unsafe { - libsodium_sys::sodium_compare(x.as_ptr() as *const _, y.as_ptr() as *const _, size_of_val(&y)) + libsodium_sys::sodium_compare( + x.as_ptr() as *const _, + y.as_ptr() as *const _, + size_of_val(&y), + ) }); } @@ -97,7 +104,11 @@ fn libc_memcmp_eq_bench(b: &mut Bencher) { let y: [u8; 1025] = [9; 1025]; b.iter(|| unsafe { - libc::memcmp(x.as_ptr() as *const c_void, y.as_ptr() as *const c_void, size_of_val(&y)) + libc::memcmp( + x.as_ptr() as *const c_void, + y.as_ptr() as *const c_void, + size_of_val(&y), + ) }); } @@ -107,6 +118,10 @@ fn libc_memcmp_nq_bench(b: &mut Bencher) { let z: [u8; 1025] = [3; 1025]; b.iter(|| unsafe { - libc::memcmp(x.as_ptr() as *const c_void, z.as_ptr() as *const c_void, size_of_val(&z)) + libc::memcmp( + x.as_ptr() as *const c_void, + z.as_ptr() as *const c_void, + size_of_val(&z), + ) }); } diff --git a/memsec-test/benches/memzero.rs b/memsec-test/benches/memzero.rs index 339d1ef..41db820 100644 --- a/memsec-test/benches/memzero.rs +++ b/memsec-test/benches/memzero.rs @@ -2,8 +2,8 @@ extern crate test; -use test::Bencher; use std::mem::size_of_val; +use test::Bencher; #[bench] fn ptr_write_zeroed_bench(b: &mut Bencher) { @@ -19,9 +19,7 @@ fn ptr_write_zeroed_bench(b: &mut Bencher) { fn memsec_memzero_bench(b: &mut Bencher) { let mut x: [u8; 1025] = [0; 1025]; - b.iter(|| unsafe { - memsec::memzero(x.as_mut_ptr(), size_of_val(&x)) - }); + b.iter(|| unsafe { memsec::memzero(x.as_mut_ptr(), size_of_val(&x)) }); } #[cfg(unix)] @@ -29,7 +27,5 @@ fn memsec_memzero_bench(b: &mut Bencher) { fn libsodium_memzero_bench(b: &mut Bencher) { let mut x: [u8; 1025] = [0; 1025]; - b.iter(|| unsafe { - libsodium_sys::sodium_memzero(x.as_mut_ptr() as *mut _, size_of_val(&x)) - }); + b.iter(|| unsafe { libsodium_sys::sodium_memzero(x.as_mut_ptr() as *mut _, size_of_val(&x)) }); } diff --git a/memsec-test/tests/allocext_linux.rs b/memsec-test/tests/allocext_linux.rs index c434340..139b2a0 100644 --- a/memsec-test/tests/allocext_linux.rs +++ b/memsec-test/tests/allocext_linux.rs @@ -3,7 +3,6 @@ use std::ptr::NonNull; - #[test] fn memfd_secret_u64_test() { unsafe { @@ -84,3 +83,187 @@ fn memfd_secret_mprotect_1_test() { memsec::free_memfd_secret(x); } } + +procspawn::enable_test_support!(); + +use std::time::Duration; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +enum Offset { + AddOffset(usize), + AddPages(usize), + SubOffset(usize), + SubPages(usize), + Nop, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] +enum TestState { + Init, + Allocate, + Operation, + Free, +} + +/// Attempts to +#[cfg(unix)] +fn attempt_write_in_region( + offset: Offset, + end_process_normally: bool, + trigger_states: Vec, +) { + let (cmd_server, cmd_serv_name) = ipc_channel::ipc::IpcOneShotServer::new().unwrap(); + let (ack_server, ack_server_name) = ipc_channel::ipc::IpcOneShotServer::new().unwrap(); + + //Create an external process + let handle = procspawn::spawn( + (offset, cmd_serv_name, ack_server_name), + |(operation, cmd_server_name, ack_server_name)| unsafe { + //Setup IPC channels for recieving commands + let (tx_cmd, rx_cmd) = ipc_channel::ipc::channel().unwrap(); + let cmd_server = ipc_channel::ipc::IpcSender::connect(cmd_server_name).unwrap(); + cmd_server.send(tx_cmd).unwrap(); + + //Setup IPC channels for acknowledging state completion + let (tx_ack, rx_ack) = ipc_channel::ipc::channel().unwrap(); + let ack_server = ipc_channel::ipc::IpcSender::connect(ack_server_name).unwrap(); + ack_server.send(rx_ack).unwrap(); + + let mut page_size = None; + let mut p: Option> = None; + + loop { + let rval = rx_cmd.recv().unwrap(); + + match rval { + TestState::Init => { + page_size = Some(libc::sysconf(libc::_SC_PAGESIZE) as usize); + tx_ack.send(rval).unwrap(); + } + + TestState::Allocate => { + p = Some(memsec::memfd_secret().unwrap()); + tx_ack.send(rval).unwrap(); + } + + TestState::Operation => { + let ptr = p.unwrap().as_ptr() as *mut u8; + + match operation { + Offset::AddOffset(offset) => { + let page_after = ptr.add(offset); + *page_after = 0x01; + } + Offset::SubOffset(offset) => { + let page_before = ptr.sub(offset); + *page_before = 0x01; + } + Offset::Nop => { + *ptr = 0x01; + } + + Offset::AddPages(pages) => { + let page_after = ptr.add(pages * page_size.unwrap()); + *page_after = 0x01; + } + Offset::SubPages(pages) => { + let page_before = ptr.sub(pages * page_size.unwrap()); + *page_before = 0x01; + } + } + tx_ack.send(rval).unwrap(); + } + TestState::Free => { + memsec::free_memfd_secret(p.unwrap()); + tx_ack.send(rval).unwrap(); + return; + } + } + } + }, + ); + + let (_, tx): (_, ipc_channel::ipc::IpcSender) = cmd_server.accept().unwrap(); + + let (_, rx): (_, ipc_channel::ipc::IpcReceiver) = ack_server.accept().unwrap(); + + for &state in trigger_states[..trigger_states.len() - 1].iter() { + tx.send(state).unwrap(); + assert_eq!(state, rx.try_recv_timeout(Duration::from_secs(1)).unwrap()); + } + + let state = trigger_states[trigger_states.len() - 1]; + tx.send(state).unwrap(); + + //If the process is expected to end normally, then the last state should be received + if end_process_normally { + assert_eq!(state, rx.try_recv_timeout(Duration::from_secs(1)).unwrap()); + } + + let r = handle.join(); + + assert!(r.is_ok() == end_process_normally); +} + +#[cfg(unix)] +#[test] +fn memfd_secret_probe_normal() { + attempt_write_in_region( + Offset::Nop, + true, + vec![ + TestState::Init, + TestState::Allocate, + TestState::Operation, + TestState::Free, + ], + ); +} + +#[cfg(unix)] +#[test] +fn memfd_secret_probe_outside_limits_canary() { + //Canary changes crash the process + attempt_write_in_region( + Offset::SubOffset(1), + false, + vec![ + TestState::Init, + TestState::Allocate, + TestState::Operation, + TestState::Free, + ], + ); +} + +#[cfg(unix)] +#[test] +fn memfd_secret_probe_outside_limits_page_above() { + attempt_write_in_region( + Offset::SubPages(1), + false, + vec![TestState::Init, TestState::Allocate, TestState::Operation], + ); +} + +#[cfg(unix)] +#[test] +fn memfd_secret_probe_outside_limits_two_pages_above() { + attempt_write_in_region( + Offset::SubPages(2), + false, + vec![TestState::Init, TestState::Allocate, TestState::Operation], + ); +} + +#[cfg(unix)] +#[test] +fn memfd_secret_probe_outside_limits_page_after_above() { + attempt_write_in_region( + Offset::AddPages(1), + false, + vec![TestState::Init, TestState::Allocate, TestState::Operation], + ); +} diff --git a/memsec-test/tests/malloc.rs b/memsec-test/tests/malloc.rs index f4da693..5f87391 100644 --- a/memsec-test/tests/malloc.rs +++ b/memsec-test/tests/malloc.rs @@ -2,7 +2,6 @@ use std::ptr::NonNull; - #[test] fn malloc_u64_test() { unsafe { @@ -83,3 +82,186 @@ fn malloc_mprotect_1_test() { memsec::free(x); } } + +procspawn::enable_test_support!(); + +use std::time::Duration; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +enum Offset { + AddOffset(usize), + AddPages(usize), + SubOffset(usize), + SubPages(usize), + Nop, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] +enum TestState { + Init, + Allocate, + Operation, + Free, +} + +#[cfg(unix)] +fn attempt_write_in_region( + offset: Offset, + end_process_normally: bool, + trigger_states: Vec, +) { + let (cmd_server, cmd_serv_name) = ipc_channel::ipc::IpcOneShotServer::new().unwrap(); + let (ack_server, ack_server_name) = ipc_channel::ipc::IpcOneShotServer::new().unwrap(); + + //Create an external process + let handle = procspawn::spawn( + (offset, cmd_serv_name, ack_server_name), + |(operation, cmd_server_name, ack_server_name)| unsafe { + //Setup IPC channels for recieving commands + let (tx_cmd, rx_cmd) = ipc_channel::ipc::channel().unwrap(); + let cmd_server = ipc_channel::ipc::IpcSender::connect(cmd_server_name).unwrap(); + cmd_server.send(tx_cmd).unwrap(); + + //Setup IPC channels for acknowledging state completion + let (tx_ack, rx_ack) = ipc_channel::ipc::channel().unwrap(); + let ack_server = ipc_channel::ipc::IpcSender::connect(ack_server_name).unwrap(); + ack_server.send(rx_ack).unwrap(); + + let mut page_size = None; + let mut p: Option> = None; + + loop { + let rval = rx_cmd.recv().unwrap(); + + match rval { + TestState::Init => { + page_size = Some(libc::sysconf(libc::_SC_PAGESIZE) as usize); + tx_ack.send(rval).unwrap(); + } + + TestState::Allocate => { + p = Some(memsec::malloc().unwrap()); + tx_ack.send(rval).unwrap(); + } + + TestState::Operation => { + let ptr = p.unwrap().as_ptr() as *mut u8; + + match operation { + Offset::AddOffset(offset) => { + let page_after = ptr.add(offset); + *page_after = 0x01; + } + Offset::SubOffset(offset) => { + let page_before = ptr.sub(offset); + *page_before = 0x01; + } + Offset::Nop => { + *ptr = 0x01; + } + + Offset::AddPages(pages) => { + let page_after = ptr.add(pages * page_size.unwrap()); + *page_after = 0x01; + } + Offset::SubPages(pages) => { + let page_before = ptr.sub(pages * page_size.unwrap()); + *page_before = 0x01; + } + } + tx_ack.send(rval).unwrap(); + } + TestState::Free => { + memsec::free(p.unwrap()); + tx_ack.send(rval).unwrap(); + return; + } + } + } + }, + ); + + let (_, tx): (_, ipc_channel::ipc::IpcSender) = cmd_server.accept().unwrap(); + + let (_, rx): (_, ipc_channel::ipc::IpcReceiver) = ack_server.accept().unwrap(); + + for &state in trigger_states[..trigger_states.len() - 1].iter() { + tx.send(state).unwrap(); + assert_eq!(state, rx.try_recv_timeout(Duration::from_secs(1)).unwrap()); + } + + let state = trigger_states[trigger_states.len() - 1]; + tx.send(state).unwrap(); + + //If the process is expected to end normally, then the last state should be received + if end_process_normally { + assert_eq!(state, rx.try_recv_timeout(Duration::from_secs(1)).unwrap()); + } + + let r = handle.join(); + + assert!(r.is_ok() == end_process_normally); +} + +#[cfg(unix)] +#[test] +fn malloc_probe_outside_normal() { + attempt_write_in_region( + Offset::Nop, + true, + vec![ + TestState::Init, + TestState::Allocate, + TestState::Operation, + TestState::Free, + ], + ); +} + +#[cfg(unix)] +#[test] +fn malloc_probe_outside_limits_canary() { + //Canary changes crash the process + attempt_write_in_region( + Offset::SubOffset(1), + false, + vec![ + TestState::Init, + TestState::Allocate, + TestState::Operation, + TestState::Free, + ], + ); +} + +#[cfg(unix)] +#[test] +fn malloc_probe_outside_limits_page_above() { + attempt_write_in_region( + Offset::SubPages(1), + false, + vec![TestState::Init, TestState::Allocate, TestState::Operation], + ); +} + +#[cfg(unix)] +#[test] +fn malloc_probe_outside_limits_two_pages_above() { + attempt_write_in_region( + Offset::SubPages(2), + false, + vec![TestState::Init, TestState::Allocate, TestState::Operation], + ); +} + +#[cfg(unix)] +#[test] +fn malloc_probe_outside_limits_page_after_above() { + attempt_write_in_region( + Offset::AddPages(1), + false, + vec![TestState::Init, TestState::Allocate, TestState::Operation], + ); +} diff --git a/memsec-test/tests/tests.rs b/memsec-test/tests/tests.rs index feb5ec5..e87774d 100644 --- a/memsec-test/tests/tests.rs +++ b/memsec-test/tests/tests.rs @@ -1,6 +1,5 @@ -use std::{ mem, cmp }; use quickcheck::quickcheck; - +use std::{cmp, mem}; #[test] fn memzero_test() { @@ -10,7 +9,10 @@ fn memzero_test() { assert_eq!(x, [0; 16]); x.clone_from_slice(&[1; 16]); assert_eq!(x, [1; 16]); - memsec::memzero(x[1..11].as_mut_ptr() as *mut u8, 10 * mem::size_of_val(&x[0])); + memsec::memzero( + x[1..11].as_mut_ptr() as *mut u8, + 10 * mem::size_of_val(&x[0]), + ); assert_eq!(x, [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1]); } } @@ -20,15 +22,11 @@ fn memeq_test() { #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] fn memeq(x: Vec, y: Vec) -> bool { unsafe { - let memsec_output = memsec::memeq( - x.as_ptr(), - y.as_ptr(), - cmp::min(x.len(), y.len()) - ); + let memsec_output = memsec::memeq(x.as_ptr(), y.as_ptr(), cmp::min(x.len(), y.len())); let libc_output = libc::memcmp( x.as_ptr() as *const libc::c_void, y.as_ptr() as *const libc::c_void, - cmp::min(x.len(), y.len()) + cmp::min(x.len(), y.len()), ) == 0; memsec_output == libc_output } @@ -41,15 +39,11 @@ fn memcmp_test() { #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] fn memcmp(x: Vec, y: Vec) -> bool { unsafe { - let memsec_output = memsec::memcmp( - x.as_ptr(), - y.as_ptr(), - cmp::min(x.len(), y.len()) - ); + let memsec_output = memsec::memcmp(x.as_ptr(), y.as_ptr(), cmp::min(x.len(), y.len())); let libc_output = libc::memcmp( x.as_ptr() as *const libc::c_void, y.as_ptr() as *const libc::c_void, - cmp::min(x.len(), y.len()) + cmp::min(x.len(), y.len()), ); (memsec_output > 0) == (libc_output > 0) && (memsec_output < 0) == (libc_output < 0) From 7e185b53c591444cf03437a15cd3bec1570fa5c9 Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Thu, 30 May 2024 15:59:07 +0530 Subject: [PATCH 08/16] Add memfd secret bench --- memsec-test/benches/malloc.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/memsec-test/benches/malloc.rs b/memsec-test/benches/malloc.rs index 812fac9..aff63a0 100644 --- a/memsec-test/benches/malloc.rs +++ b/memsec-test/benches/malloc.rs @@ -14,6 +14,15 @@ fn memsec_malloc(b: &mut Bencher) { }); } +#[cfg(unix)] +#[bench] +fn memsec_memfd_secret(b: &mut Bencher) { + b.iter(|| unsafe { + let ptr: NonNull<[u8; 512]> = memsec::memfd_secret().unwrap(); + memsec::free_memfd_secret(ptr); + }); +} + #[cfg(unix)] #[bench] fn libsodium_malloc(b: &mut Bencher) { From 30e7d8225109636bed8163a8797602f6dcaec7b3 Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Tue, 4 Jun 2024 12:43:09 +0530 Subject: [PATCH 09/16] Review: Add memzero on free, assert unprotected_size + fd <= PAGE_SIZE --- memsec-test/tests/allocext_linux.rs | 2 -- src/alloc/allocext/linux.rs | 7 +++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/memsec-test/tests/allocext_linux.rs b/memsec-test/tests/allocext_linux.rs index 139b2a0..9d7a737 100644 --- a/memsec-test/tests/allocext_linux.rs +++ b/memsec-test/tests/allocext_linux.rs @@ -107,8 +107,6 @@ enum TestState { Free, } -/// Attempts to -#[cfg(unix)] fn attempt_write_in_region( offset: Offset, end_process_normally: bool, diff --git a/src/alloc/allocext/linux.rs b/src/alloc/allocext/linux.rs index 46315fe..5012e63 100644 --- a/src/alloc/allocext/linux.rs +++ b/src/alloc/allocext/linux.rs @@ -9,8 +9,6 @@ use self::memfd_secret_alloc::*; mod memfd_secret_alloc { use core::convert::TryInto; - use std::{io, println}; - use super::*; #[inline] @@ -42,6 +40,9 @@ mod memfd_secret_alloc { unsafe fn _memfd_secret(size: usize) -> Option<*mut u8> { ALLOC_INIT.call_once(|| alloc_init()); + //Assert size of unprotected_size (usize) and fd (i32) is less than PAGE_SIZE before allocating memory + assert!(size_of::() + size_of::() <= PAGE_SIZE); + if size >= ::core::usize::MAX - PAGE_SIZE * 4 { return None; } @@ -119,6 +120,8 @@ pub unsafe fn free_memfd_secret(memptr: NonNull) { let total_size = PAGE_SIZE + PAGE_SIZE + unprotected_size + PAGE_SIZE; _mprotect(base_ptr, total_size, Prot::ReadWrite); + crate::memzero(base_ptr, total_size); + let res = libc::munmap(base_ptr as *mut c_void, total_size); if res < 0 { abort(); From 1f143c24310a46b30e79f9be9063bb2ebd54c36b Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:04:27 +0530 Subject: [PATCH 10/16] Review: Match memzero to only erase unprotected memory --- src/alloc/allocext/linux.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/alloc/allocext/linux.rs b/src/alloc/allocext/linux.rs index 5012e63..100afb5 100644 --- a/src/alloc/allocext/linux.rs +++ b/src/alloc/allocext/linux.rs @@ -120,7 +120,7 @@ pub unsafe fn free_memfd_secret(memptr: NonNull) { let total_size = PAGE_SIZE + PAGE_SIZE + unprotected_size + PAGE_SIZE; _mprotect(base_ptr, total_size, Prot::ReadWrite); - crate::memzero(base_ptr, total_size); + crate::memzero(unprotected_ptr,unprotected_size); let res = libc::munmap(base_ptr as *mut c_void, total_size); if res < 0 { From 23e59ef1e99ee801260aee402200717df4c8d668 Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Tue, 4 Jun 2024 19:28:01 +0530 Subject: [PATCH 11/16] Remove/add lints for CI --- src/alloc/allocext/linux.rs | 4 ++-- src/alloc/mod.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/alloc/allocext/linux.rs b/src/alloc/allocext/linux.rs index 100afb5..154ef62 100644 --- a/src/alloc/allocext/linux.rs +++ b/src/alloc/allocext/linux.rs @@ -8,8 +8,8 @@ use core::slice; use self::memfd_secret_alloc::*; mod memfd_secret_alloc { - use core::convert::TryInto; use super::*; + use core::convert::TryInto; #[inline] pub unsafe fn alloc_memfd_secret(size: usize) -> Option<(NonNull, libc::c_int)> { @@ -120,7 +120,7 @@ pub unsafe fn free_memfd_secret(memptr: NonNull) { let total_size = PAGE_SIZE + PAGE_SIZE + unprotected_size + PAGE_SIZE; _mprotect(base_ptr, total_size, Prot::ReadWrite); - crate::memzero(unprotected_ptr,unprotected_size); + crate::memzero(unprotected_ptr, unprotected_size); let res = libc::munmap(base_ptr as *mut c_void, total_size); if res < 0 { diff --git a/src/alloc/mod.rs b/src/alloc/mod.rs index 40e65c0..77dd6cd 100644 --- a/src/alloc/mod.rs +++ b/src/alloc/mod.rs @@ -42,6 +42,7 @@ unsafe fn alloc_init() { PAGE_MASK = PAGE_SIZE - 1; + #[allow(static_mut_refs)] getrandom(&mut CANARY).unwrap(); } From 6b17d75abbdccdf227c9a76ff8ed634bd9db24d6 Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Wed, 5 Jun 2024 10:45:19 +0530 Subject: [PATCH 12/16] Missed file: Remove lint for CI --- memsec-test/tests/tests.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/memsec-test/tests/tests.rs b/memsec-test/tests/tests.rs index e87774d..05420eb 100644 --- a/memsec-test/tests/tests.rs +++ b/memsec-test/tests/tests.rs @@ -19,7 +19,6 @@ fn memzero_test() { #[test] fn memeq_test() { - #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] fn memeq(x: Vec, y: Vec) -> bool { unsafe { let memsec_output = memsec::memeq(x.as_ptr(), y.as_ptr(), cmp::min(x.len(), y.len())); @@ -36,7 +35,6 @@ fn memeq_test() { #[test] fn memcmp_test() { - #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] fn memcmp(x: Vec, y: Vec) -> bool { unsafe { let memsec_output = memsec::memcmp(x.as_ptr(), y.as_ptr(), cmp::min(x.len(), y.len())); From 06837678ca3c263be9f5f77d9604d5bbc6abed82 Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Wed, 5 Jun 2024 11:34:22 +0530 Subject: [PATCH 13/16] Ignore incompatible tests on Windows CI --- memsec-test/tests/malloc.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/memsec-test/tests/malloc.rs b/memsec-test/tests/malloc.rs index 5f87391..edb1e5d 100644 --- a/memsec-test/tests/malloc.rs +++ b/memsec-test/tests/malloc.rs @@ -83,10 +83,12 @@ fn malloc_mprotect_1_test() { } } -procspawn::enable_test_support!(); +#[cfg(unix)] +mod unix_only { +use std::ptr::NonNull; +procspawn::enable_test_support!(); use std::time::Duration; - use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] @@ -106,7 +108,6 @@ enum TestState { Free, } -#[cfg(unix)] fn attempt_write_in_region( offset: Offset, end_process_normally: bool, @@ -205,7 +206,6 @@ fn attempt_write_in_region( assert!(r.is_ok() == end_process_normally); } -#[cfg(unix)] #[test] fn malloc_probe_outside_normal() { attempt_write_in_region( @@ -220,7 +220,6 @@ fn malloc_probe_outside_normal() { ); } -#[cfg(unix)] #[test] fn malloc_probe_outside_limits_canary() { //Canary changes crash the process @@ -236,7 +235,6 @@ fn malloc_probe_outside_limits_canary() { ); } -#[cfg(unix)] #[test] fn malloc_probe_outside_limits_page_above() { attempt_write_in_region( @@ -246,7 +244,6 @@ fn malloc_probe_outside_limits_page_above() { ); } -#[cfg(unix)] #[test] fn malloc_probe_outside_limits_two_pages_above() { attempt_write_in_region( @@ -256,7 +253,6 @@ fn malloc_probe_outside_limits_two_pages_above() { ); } -#[cfg(unix)] #[test] fn malloc_probe_outside_limits_page_after_above() { attempt_write_in_region( @@ -265,3 +261,4 @@ fn malloc_probe_outside_limits_page_after_above() { vec![TestState::Init, TestState::Allocate, TestState::Operation], ); } +} \ No newline at end of file From 063c852136ddbfda2846735e933e6bb85a1efd06 Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:47:02 +0530 Subject: [PATCH 14/16] Move malloc test to differnet files, allow lint for CI pass --- memsec-test/tests/malloc_unix_only.rs | 177 ++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 178 insertions(+) create mode 100644 memsec-test/tests/malloc_unix_only.rs diff --git a/memsec-test/tests/malloc_unix_only.rs b/memsec-test/tests/malloc_unix_only.rs new file mode 100644 index 0000000..a4a27db --- /dev/null +++ b/memsec-test/tests/malloc_unix_only.rs @@ -0,0 +1,177 @@ +#![cfg(unix)] + +use std::ptr::NonNull; +procspawn::enable_test_support!(); +use std::time::Duration; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +enum Offset { + AddOffset(usize), + AddPages(usize), + SubOffset(usize), + SubPages(usize), + Nop, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] +enum TestState { + Init, + Allocate, + Operation, + Free, +} + +fn attempt_write_in_region( + offset: Offset, + end_process_normally: bool, + trigger_states: Vec, +) { + let (cmd_server, cmd_serv_name) = ipc_channel::ipc::IpcOneShotServer::new().unwrap(); + let (ack_server, ack_server_name) = ipc_channel::ipc::IpcOneShotServer::new().unwrap(); + + //Create an external process + let handle = procspawn::spawn( + (offset, cmd_serv_name, ack_server_name), + |(operation, cmd_server_name, ack_server_name)| unsafe { + //Setup IPC channels for recieving commands + let (tx_cmd, rx_cmd) = ipc_channel::ipc::channel().unwrap(); + let cmd_server = ipc_channel::ipc::IpcSender::connect(cmd_server_name).unwrap(); + cmd_server.send(tx_cmd).unwrap(); + + //Setup IPC channels for acknowledging state completion + let (tx_ack, rx_ack) = ipc_channel::ipc::channel().unwrap(); + let ack_server = ipc_channel::ipc::IpcSender::connect(ack_server_name).unwrap(); + ack_server.send(rx_ack).unwrap(); + + let mut page_size = None; + let mut p: Option> = None; + + loop { + let rval = rx_cmd.recv().unwrap(); + + match rval { + TestState::Init => { + page_size = Some(libc::sysconf(libc::_SC_PAGESIZE) as usize); + tx_ack.send(rval).unwrap(); + } + + TestState::Allocate => { + p = Some(memsec::malloc().unwrap()); + tx_ack.send(rval).unwrap(); + } + + TestState::Operation => { + let ptr = p.unwrap().as_ptr() as *mut u8; + + match operation { + Offset::AddOffset(offset) => { + let page_after = ptr.add(offset); + *page_after = 0x01; + } + Offset::SubOffset(offset) => { + let page_before = ptr.sub(offset); + *page_before = 0x01; + } + Offset::Nop => { + *ptr = 0x01; + } + + Offset::AddPages(pages) => { + let page_after = ptr.add(pages * page_size.unwrap()); + *page_after = 0x01; + } + Offset::SubPages(pages) => { + let page_before = ptr.sub(pages * page_size.unwrap()); + *page_before = 0x01; + } + } + tx_ack.send(rval).unwrap(); + } + TestState::Free => { + memsec::free(p.unwrap()); + tx_ack.send(rval).unwrap(); + return; + } + } + } + }, + ); + + let (_, tx): (_, ipc_channel::ipc::IpcSender) = cmd_server.accept().unwrap(); + + let (_, rx): (_, ipc_channel::ipc::IpcReceiver) = ack_server.accept().unwrap(); + + for &state in trigger_states[..trigger_states.len() - 1].iter() { + tx.send(state).unwrap(); + assert_eq!(state, rx.try_recv_timeout(Duration::from_secs(1)).unwrap()); + } + + let state = trigger_states[trigger_states.len() - 1]; + tx.send(state).unwrap(); + + //If the process is expected to end normally, then the last state should be received + if end_process_normally { + assert_eq!(state, rx.try_recv_timeout(Duration::from_secs(1)).unwrap()); + } + + let r = handle.join(); + + assert!(r.is_ok() == end_process_normally); +} + +#[test] +fn malloc_probe_outside_normal() { + attempt_write_in_region( + Offset::Nop, + true, + vec![ + TestState::Init, + TestState::Allocate, + TestState::Operation, + TestState::Free, + ], + ); +} + +#[test] +fn malloc_probe_outside_limits_canary() { + //Canary changes crash the process + attempt_write_in_region( + Offset::SubOffset(1), + false, + vec![ + TestState::Init, + TestState::Allocate, + TestState::Operation, + TestState::Free, + ], + ); +} + +#[test] +fn malloc_probe_outside_limits_page_above() { + attempt_write_in_region( + Offset::SubPages(1), + false, + vec![TestState::Init, TestState::Allocate, TestState::Operation], + ); +} + +#[test] +fn malloc_probe_outside_limits_two_pages_above() { + attempt_write_in_region( + Offset::SubPages(2), + false, + vec![TestState::Init, TestState::Allocate, TestState::Operation], + ); +} + +#[test] +fn malloc_probe_outside_limits_page_after_above() { + attempt_write_in_region( + Offset::AddPages(1), + false, + vec![TestState::Init, TestState::Allocate, TestState::Operation], + ); +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index de0526c..9095eb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +#![cfg_attr(feature = "nightly", allow(internal_features))] #![cfg_attr(feature = "nightly", feature(core_intrinsics))] #![allow(clippy::missing_safety_doc)] From eba29d5ec70055bbbd5cc8e27e1ec702345c67e5 Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:16:57 +0530 Subject: [PATCH 15/16] Move malloc test to different file --- memsec-test/tests/malloc.rs | 180 ------------------------------------ 1 file changed, 180 deletions(-) diff --git a/memsec-test/tests/malloc.rs b/memsec-test/tests/malloc.rs index edb1e5d..b7f6860 100644 --- a/memsec-test/tests/malloc.rs +++ b/memsec-test/tests/malloc.rs @@ -82,183 +82,3 @@ fn malloc_mprotect_1_test() { memsec::free(x); } } - -#[cfg(unix)] -mod unix_only { - -use std::ptr::NonNull; -procspawn::enable_test_support!(); -use std::time::Duration; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -enum Offset { - AddOffset(usize), - AddPages(usize), - SubOffset(usize), - SubPages(usize), - Nop, -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] -enum TestState { - Init, - Allocate, - Operation, - Free, -} - -fn attempt_write_in_region( - offset: Offset, - end_process_normally: bool, - trigger_states: Vec, -) { - let (cmd_server, cmd_serv_name) = ipc_channel::ipc::IpcOneShotServer::new().unwrap(); - let (ack_server, ack_server_name) = ipc_channel::ipc::IpcOneShotServer::new().unwrap(); - - //Create an external process - let handle = procspawn::spawn( - (offset, cmd_serv_name, ack_server_name), - |(operation, cmd_server_name, ack_server_name)| unsafe { - //Setup IPC channels for recieving commands - let (tx_cmd, rx_cmd) = ipc_channel::ipc::channel().unwrap(); - let cmd_server = ipc_channel::ipc::IpcSender::connect(cmd_server_name).unwrap(); - cmd_server.send(tx_cmd).unwrap(); - - //Setup IPC channels for acknowledging state completion - let (tx_ack, rx_ack) = ipc_channel::ipc::channel().unwrap(); - let ack_server = ipc_channel::ipc::IpcSender::connect(ack_server_name).unwrap(); - ack_server.send(rx_ack).unwrap(); - - let mut page_size = None; - let mut p: Option> = None; - - loop { - let rval = rx_cmd.recv().unwrap(); - - match rval { - TestState::Init => { - page_size = Some(libc::sysconf(libc::_SC_PAGESIZE) as usize); - tx_ack.send(rval).unwrap(); - } - - TestState::Allocate => { - p = Some(memsec::malloc().unwrap()); - tx_ack.send(rval).unwrap(); - } - - TestState::Operation => { - let ptr = p.unwrap().as_ptr() as *mut u8; - - match operation { - Offset::AddOffset(offset) => { - let page_after = ptr.add(offset); - *page_after = 0x01; - } - Offset::SubOffset(offset) => { - let page_before = ptr.sub(offset); - *page_before = 0x01; - } - Offset::Nop => { - *ptr = 0x01; - } - - Offset::AddPages(pages) => { - let page_after = ptr.add(pages * page_size.unwrap()); - *page_after = 0x01; - } - Offset::SubPages(pages) => { - let page_before = ptr.sub(pages * page_size.unwrap()); - *page_before = 0x01; - } - } - tx_ack.send(rval).unwrap(); - } - TestState::Free => { - memsec::free(p.unwrap()); - tx_ack.send(rval).unwrap(); - return; - } - } - } - }, - ); - - let (_, tx): (_, ipc_channel::ipc::IpcSender) = cmd_server.accept().unwrap(); - - let (_, rx): (_, ipc_channel::ipc::IpcReceiver) = ack_server.accept().unwrap(); - - for &state in trigger_states[..trigger_states.len() - 1].iter() { - tx.send(state).unwrap(); - assert_eq!(state, rx.try_recv_timeout(Duration::from_secs(1)).unwrap()); - } - - let state = trigger_states[trigger_states.len() - 1]; - tx.send(state).unwrap(); - - //If the process is expected to end normally, then the last state should be received - if end_process_normally { - assert_eq!(state, rx.try_recv_timeout(Duration::from_secs(1)).unwrap()); - } - - let r = handle.join(); - - assert!(r.is_ok() == end_process_normally); -} - -#[test] -fn malloc_probe_outside_normal() { - attempt_write_in_region( - Offset::Nop, - true, - vec![ - TestState::Init, - TestState::Allocate, - TestState::Operation, - TestState::Free, - ], - ); -} - -#[test] -fn malloc_probe_outside_limits_canary() { - //Canary changes crash the process - attempt_write_in_region( - Offset::SubOffset(1), - false, - vec![ - TestState::Init, - TestState::Allocate, - TestState::Operation, - TestState::Free, - ], - ); -} - -#[test] -fn malloc_probe_outside_limits_page_above() { - attempt_write_in_region( - Offset::SubPages(1), - false, - vec![TestState::Init, TestState::Allocate, TestState::Operation], - ); -} - -#[test] -fn malloc_probe_outside_limits_two_pages_above() { - attempt_write_in_region( - Offset::SubPages(2), - false, - vec![TestState::Init, TestState::Allocate, TestState::Operation], - ); -} - -#[test] -fn malloc_probe_outside_limits_page_after_above() { - attempt_write_in_region( - Offset::AddPages(1), - false, - vec![TestState::Init, TestState::Allocate, TestState::Operation], - ); -} -} \ No newline at end of file From d02c89a53f8f59570fc925cbd9bf91267afe4f94 Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:59:59 +0530 Subject: [PATCH 16/16] Make Linux CI pass --- memsec-test/tests/malloc_unix_only.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/memsec-test/tests/malloc_unix_only.rs b/memsec-test/tests/malloc_unix_only.rs index a4a27db..aaaeb12 100644 --- a/memsec-test/tests/malloc_unix_only.rs +++ b/memsec-test/tests/malloc_unix_only.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "alloc")] #![cfg(unix)] use std::ptr::NonNull;