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..14489c1 100644 --- a/memsec-test/Cargo.toml +++ b/memsec-test/Cargo.toml @@ -12,13 +12,17 @@ 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" ] +default = [ "alloc", "use_os", "alloc_ext"] nightly = [ "memsec/nightly" ] use_os = [ "memsec/use_os" ] alloc = [ "memsec/alloc" ] +alloc_ext = [ "memsec/alloc_ext", "use_os" ] diff --git a/memsec-test/benches/malloc.rs b/memsec-test/benches/malloc.rs index 0100146..aff63a0 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 { @@ -15,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) { 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 new file mode 100644 index 0000000..9d7a737 --- /dev/null +++ b/memsec-test/tests/allocext_linux.rs @@ -0,0 +1,267 @@ +#![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); + } +} + +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::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..b7f6860 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 { diff --git a/memsec-test/tests/malloc_unix_only.rs b/memsec-test/tests/malloc_unix_only.rs new file mode 100644 index 0000000..aaaeb12 --- /dev/null +++ b/memsec-test/tests/malloc_unix_only.rs @@ -0,0 +1,178 @@ +#![cfg(feature = "alloc")] +#![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/memsec-test/tests/tests.rs b/memsec-test/tests/tests.rs index feb5ec5..05420eb 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,25 +9,23 @@ 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]); } } #[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()) - ); + 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 } @@ -38,18 +35,13 @@ 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()) - ); + 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) diff --git a/src/alloc/allocext/linux.rs b/src/alloc/allocext/linux.rs new file mode 100644 index 0000000..154ef62 --- /dev/null +++ b/src/alloc/allocext/linux.rs @@ -0,0 +1,134 @@ +extern crate std; +use self::std::process::abort; +use crate::{alloc::*, Prot}; +use core::mem::{self, size_of}; +use core::ptr::{self, NonNull}; +use core::slice; + +use self::memfd_secret_alloc::*; + +mod memfd_secret_alloc { + use super::*; + use core::convert::TryInto; + + #[inline] + 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)?; + + // 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).map(|ptr| (ptr, fd)) + } +} + +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; + } + + // 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, 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 + // 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); + 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); + + Some(user_ptr) +} + +/// Linux specific `memfd_secret` backed allocation +#[inline] +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] +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 +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 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) { + abort(); + } + + // free + let total_size = PAGE_SIZE + PAGE_SIZE + unprotected_size + PAGE_SIZE; + _mprotect(base_ptr, total_size, Prot::ReadWrite); + + crate::memzero(unprotected_ptr, unprotected_size); + + let res = libc::munmap(base_ptr as *mut c_void, total_size); + if res < 0 { + abort(); + } + + let res = libc::close(fd); + if res < 0 { + abort(); + } +} 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::*; diff --git a/src/alloc.rs b/src/alloc/mod.rs similarity index 91% rename from src/alloc.rs rename to src/alloc/mod.rs index 2b4f991..77dd6cd 100644 --- a/src/alloc.rs +++ b/src/alloc/mod.rs @@ -2,16 +2,16 @@ #![cfg(feature = "alloc")] -extern crate std; +pub mod allocext; +extern crate std; +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; @@ -41,14 +42,14 @@ unsafe fn alloc_init() { PAGE_MASK = PAGE_SIZE - 1; + #[allow(static_mut_refs)] 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,7 +65,6 @@ mod raw_alloc { } } - // -- mprotect -- /// Prot enum. @@ -105,7 +105,6 @@ pub mod Prot { pub const TargetsNoUpdate: Ty = windows_sys::Win32::System::Memory::PAGE_TARGETS_NO_UPDATE; } - /// Unix `mprotect`. #[cfg(unix)] #[inline] @@ -121,7 +120,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 +131,6 @@ pub unsafe fn mprotect(memptr: NonNull, prot: Prot::Ty) -> bool { _mprotect(unprotected_ptr, unprotected_size, prot) } - // -- malloc / free -- #[inline] @@ -167,7 +164,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); @@ -184,21 +185,19 @@ unsafe fn _malloc(size: usize) -> Option<*mut u8> { /// 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)) + }) } /// Secure `free`. diff --git a/src/lib.rs b/src/lib.rs index a1814d0..9095eb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,22 @@ #![no_std] - +#![cfg_attr(feature = "nightly", allow(internal_features))] #![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_ext")] +#[cfg(target_os = "linux")] +pub use alloc::allocext::{free_memfd_secret, memfd_secret, memfd_secret_sized}; // -- memcmp -- @@ -26,30 +29,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); 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 } }