pub mod mem;
pub use paste;
use errno::Errno;
use libsodium_sys as sodium;
use thiserror::Error;
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum HardError {
#[error("Failed to allocate secure memory region")]
AllocationFailed(Errno),
#[error("Failed to mark memory region as noaccess (syscall may not be available)")]
MprotectNoAccessFailed(Errno),
#[error("Failed to mark memory region as readonly (syscall may not be available)")]
MprotectReadOnlyFailed(Errno),
#[error("Failed to mark memory region as read/write (syscall may not be available)")]
MprotectReadWriteFailed(Errno),
#[error("Failed to initialise libsodium")]
InitFailed,
}
pub trait Buffer
where
Self: Sized,
{
const SIZE: usize;
fn new() -> Result<Self, HardError>;
}
pub trait BufferMut: Buffer
where
Self: Sized,
{
#[cfg(feature = "restricted-types")]
type NoAccess: BufferNoAccess;
#[cfg(feature = "restricted-types")]
type ReadOnly: BufferReadOnly;
fn zero(&mut self);
fn try_clone(&self) -> Result<Self, HardError>;
#[cfg(feature = "restricted-types")]
fn into_noaccess(self) -> Result<Self::NoAccess, HardError>;
#[cfg(feature = "restricted-types")]
fn into_readonly(self) -> Result<Self::ReadOnly, HardError>;
}
#[cfg(feature = "restricted-types")]
pub trait BufferNoAccess: Buffer
where
Self: Sized,
{
type ReadWrite: BufferMut;
type ReadOnly: BufferReadOnly;
fn into_mut(self) -> Result<Self::ReadWrite, HardError>;
fn into_readonly(self) -> Result<Self::ReadOnly, HardError>;
}
#[cfg(feature = "restricted-types")]
pub trait BufferReadOnly: Buffer
where
Self: Sized,
{
type ReadWrite: BufferMut;
type NoAccess: BufferNoAccess;
fn try_clone(&self) -> Result<Self, HardError>;
fn into_mut(self) -> Result<Self::ReadWrite, HardError>;
fn into_noaccess(self) -> Result<Self::NoAccess, HardError>;
}
#[doc(hidden)]
pub unsafe trait BufferAsPtr: Buffer
where
Self: Sized,
{
unsafe fn as_ptr(&self) -> std::ptr::NonNull<()>;
}
#[macro_export]
#[doc(hidden)]
macro_rules! _buffer_common_impl {
($name:ident, $size:expr) => {
impl Drop for $name {
fn drop(&mut self) {
unsafe {
$crate::mem::free(self.ptr);
}
}
}
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! _buffer_immutable_impl {
($name:ident, $size:expr) => {
#[doc(hidden)]
unsafe impl $crate::BufferAsPtr for $name {
unsafe fn as_ptr(&self) -> std::ptr::NonNull<()> {
self.ptr.cast()
}
}
impl std::convert::AsRef<[u8; $size]> for $name {
fn as_ref(&self) -> &[u8; $size] {
unsafe { self.ptr.as_ref() }
}
}
impl std::borrow::Borrow<[u8; $size]> for $name {
fn borrow(&self) -> &[u8; $size] {
unsafe { self.ptr.as_ref() }
}
}
impl std::fmt::Debug for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!("{}([u8; {}])", stringify!($name), $size))
}
}
impl std::ops::Deref for $name {
type Target = [u8; $size];
fn deref(&self) -> &Self::Target {
unsafe { self.ptr.as_ref() }
}
}
impl<T: $crate::Buffer + $crate::BufferAsPtr> std::cmp::PartialEq<T> for $name {
fn eq(&self, other: &T) -> bool {
if T::SIZE != Self::SIZE {
return false;
}
unsafe { $crate::mem::memcmp(self.ptr, other.as_ptr().cast()) }
}
}
impl std::fmt::Pointer for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
<std::ptr::NonNull<[u8; $size]> as std::fmt::Pointer>::fmt(&self.ptr, f)
}
}
};
}
#[macro_export]
#[doc(hidden)]
#[cfg(feature = "restricted-types")]
macro_rules! _buffer_mutable_impl {
($name:ident, $size:expr) => {
$crate::paste::paste! {
$crate::_buffer_common_impl!($name, $size);
$crate::_buffer_immutable_impl!($name, $size);
impl $crate::Buffer for $name {
const SIZE: usize = $size;
fn new() -> Result<Self, $crate::HardError> {
$crate::init()?;
let ptr = unsafe { $crate::mem::malloc()? };
Ok(Self {
ptr,
_marker: std::marker::PhantomData,
})
}
}
impl $crate::BufferMut for $name {
type NoAccess = [<$name NoAccess>];
type ReadOnly = [<$name ReadOnly>];
fn zero(&mut self) {
unsafe { $crate::mem::memzero(self.ptr) }
}
fn try_clone(&self) -> Result<Self, $crate::HardError> {
let mut new_buf = Self::new()?;
new_buf.copy_from_slice(self.as_ref());
Ok(new_buf)
}
fn into_noaccess(self) -> Result<Self::NoAccess, $crate::HardError> {
let self_leak = std::mem::ManuallyDrop::new(self);
unsafe { $crate::mem::mprotect_noaccess(self_leak.ptr)?; }
Ok([<$name NoAccess>] {
ptr: self_leak.ptr,
_marker: std::marker::PhantomData,
})
}
fn into_readonly(self) -> Result<Self::ReadOnly, $crate::HardError> {
let self_leak = std::mem::ManuallyDrop::new(self);
unsafe { $crate::mem::mprotect_readonly(self_leak.ptr)?; }
Ok([<$name ReadOnly>] {
ptr: self_leak.ptr,
_marker: std::marker::PhantomData,
})
}
}
impl std::convert::AsMut<[u8; $size]> for $name {
fn as_mut(&mut self) -> &mut [u8; $size] {
unsafe { self.ptr.as_mut() }
}
}
impl std::borrow::BorrowMut<[u8; $size]> for $name {
fn borrow_mut(&mut self) -> &mut [u8; $size] {
unsafe { self.ptr.as_mut() }
}
}
impl std::ops::DerefMut for $name {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.ptr.as_mut() }
}
}
}
};
}
#[macro_export]
#[doc(hidden)]
#[cfg(not(feature = "restricted-types"))]
macro_rules! _buffer_mutable_impl {
($name:ident, $size:expr) => {
$crate::paste::paste! {
$crate::_buffer_common_impl!($name, $size);
$crate::_buffer_immutable_impl!($name, $size);
impl $crate::Buffer for $name {
const SIZE: usize = $size;
fn new() -> Result<Self, $crate::HardError> {
$crate::init()?;
let ptr = unsafe { $crate::mem::malloc()? };
Ok(Self {
ptr,
_marker: std::marker::PhantomData,
})
}
}
impl $crate::BufferMut for $name {
fn zero(&mut self) {
unsafe { $crate::mem::memzero(self.ptr) }
}
fn try_clone(&self) -> Result<Self, $crate::HardError> {
let mut new_buf = Self::new()?;
new_buf.copy_from_slice(self.as_ref());
Ok(new_buf)
}
}
impl std::convert::AsMut<[u8; $size]> for $name {
fn as_mut(&mut self) -> &mut [u8; $size] {
unsafe { self.ptr.as_mut() }
}
}
impl std::borrow::BorrowMut<[u8; $size]> for $name {
fn borrow_mut(&mut self) -> &mut [u8; $size] {
unsafe { self.ptr.as_mut() }
}
}
impl std::ops::DerefMut for $name {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.ptr.as_mut() }
}
}
}
};
}
#[macro_export]
#[doc(hidden)]
#[cfg(feature = "restricted-types")]
macro_rules! _buffer_noaccess_impl {
($name:ident, $size:expr) => {
$crate::paste::paste! {
$crate::_buffer_common_impl!([<$name NoAccess>], $size);
impl $crate::Buffer for [<$name NoAccess>] {
const SIZE: usize = $size;
fn new() -> Result<Self, $crate::HardError> {
$crate::init()?;
let ptr = unsafe {
let ptr = $crate::mem::malloc()?;
$crate::mem::mprotect_noaccess(ptr)?;
ptr
};
Ok(Self {
ptr,
_marker: std::marker::PhantomData,
})
}
}
impl $crate::BufferNoAccess for [<$name NoAccess>] {
type ReadWrite = $name;
type ReadOnly = [<$name ReadOnly>];
fn into_mut(self) -> Result<Self::ReadWrite, $crate::HardError> {
let self_leak = std::mem::ManuallyDrop::new(self);
unsafe { $crate::mem::mprotect_readwrite(self_leak.ptr)?; }
Ok($name {
ptr: self_leak.ptr,
_marker: std::marker::PhantomData,
})
}
fn into_readonly(self) -> Result<Self::ReadOnly, $crate::HardError> {
let self_leak = std::mem::ManuallyDrop::new(self);
unsafe { $crate::mem::mprotect_readonly(self_leak.ptr)?; }
Ok([<$name ReadOnly>] {
ptr: self_leak.ptr,
_marker: std::marker::PhantomData,
})
}
}
}
};
}
#[macro_export]
#[doc(hidden)]
#[cfg(feature = "restricted-types")]
macro_rules! _buffer_readonly_impl {
($name:ident, $size:expr) => {
$crate::paste::paste! {
$crate::_buffer_common_impl!([<$name ReadOnly>], $size);
$crate::_buffer_immutable_impl!([<$name ReadOnly>], $size);
impl $crate::Buffer for [<$name ReadOnly>] {
const SIZE: usize = $size;
fn new() -> Result<Self, $crate::HardError> {
$crate::init()?;
let ptr = unsafe {
let ptr = $crate::mem::malloc()?;
$crate::mem::mprotect_readonly(ptr)?;
ptr
};
Ok(Self {
ptr,
_marker: std::marker::PhantomData,
})
}
}
impl $crate::BufferReadOnly for [<$name ReadOnly>] {
type ReadWrite = $name;
type NoAccess = [<$name NoAccess>];
fn try_clone(&self) -> Result<Self, $crate::HardError> {
use $crate::BufferMut;
let mut new_buf = $name::new()?;
new_buf.copy_from_slice(self.as_ref());
new_buf.into_readonly()
}
fn into_mut(self) -> Result<Self::ReadWrite, $crate::HardError> {
let self_leak = std::mem::ManuallyDrop::new(self);
unsafe { $crate::mem::mprotect_readwrite(self_leak.ptr)?; }
Ok($name {
ptr: self_leak.ptr,
_marker: std::marker::PhantomData,
})
}
fn into_noaccess(self) -> Result<Self::NoAccess, $crate::HardError> {
let self_leak = std::mem::ManuallyDrop::new(self);
unsafe { $crate::mem::mprotect_noaccess(self_leak.ptr)?; }
Ok([<$name NoAccess>] {
ptr: self_leak.ptr,
_marker: std::marker::PhantomData,
})
}
}
}
};
}
#[macro_export]
#[doc(hidden)]
#[cfg(feature = "restricted-types")]
macro_rules! _buffer_type_impl {
( $( $(#[$metadata:meta])* $vis:vis $name:ident($size:expr)$(;)? )* ) => {
$(
$crate::paste::paste! {
$(#[$metadata])*
$vis struct $name {
ptr: std::ptr::NonNull<[u8; $size]>,
_marker: std::marker::PhantomData<[u8; $size]>,
}
$crate::_buffer_mutable_impl!($name, $size);
$vis struct [<$name NoAccess>] {
ptr: std::ptr::NonNull<[u8; $size]>,
_marker: std::marker::PhantomData<[u8; $size]>,
}
$crate::_buffer_noaccess_impl!($name, $size);
$vis struct [<$name ReadOnly>] {
ptr: std::ptr::NonNull<[u8; $size]>,
_marker: std::marker::PhantomData<[u8; $size]>,
}
$crate::_buffer_readonly_impl!($name, $size);
}
)*
};
}
#[macro_export]
#[doc(hidden)]
#[cfg(not(feature = "restricted-types"))]
macro_rules! _buffer_type_impl {
( $( $(#[$metadata:meta])* $vis:vis $name:ident($size:expr)$(;)? )* ) => {
$(
$crate::paste::paste! {
$(#[$metadata])*
$vis struct $name {
ptr: std::ptr::NonNull<[u8; $size]>,
_marker: std::marker::PhantomData<[u8; $size]>,
}
$crate::_buffer_mutable_impl!($name, $size);
}
)*
};
}
#[macro_export]
macro_rules! buffer_type {
( $( $(#[$metadata:meta])* $vis:vis $name:ident($size:expr)$(;)? )* ) => {
$(
$crate::_buffer_type_impl! {
$(#[$metadata])*
$vis $name($size);
}
)*
};
}
#[macro_export]
macro_rules! buffer {
($size:expr$(;)?) => {{
$crate::paste::paste! {
use $crate::Buffer;
$crate::buffer_type!([<_HardAnonBuffer $size>]($size));
[<_HardAnonBuffer $size>]::new()
}
}};
}
pub fn init() -> Result<(), HardError> {
unsafe {
if sodium::sodium_init() >= 0 {
Ok(())
} else {
Err(HardError::InitFailed)
}
}
}
#[cfg(test)]
mod tests {
use super::{buffer, buffer_type, init, Buffer, BufferMut, HardError};
#[cfg(feature = "restricted-types")]
use super::{BufferNoAccess, BufferReadOnly};
use std::borrow::{Borrow, BorrowMut};
use std::ops::DerefMut;
#[test]
fn initialise_sodium() -> Result<(), HardError> {
init()
}
#[test]
fn create_buffer_types() -> Result<(), HardError> {
buffer_type!(Buf8(8));
buffer_type!(pub Buf32(32));
buffer_type! {
Buf512(512)
}
buffer_type! {
pub Buf1MiB(1 << 20)
}
assert_eq!(Buf8::SIZE, 8);
assert_eq!(Buf32::SIZE, 32);
assert_eq!(Buf512::SIZE, 512);
assert_eq!(Buf1MiB::SIZE, 1 << 20);
let buf_8 = Buf8::new()?;
let buf_32 = Buf32::new()?;
let buf_512 = Buf512::new()?;
let buf_mib = Buf1MiB::new()?;
assert_eq!(buf_8.len(), 8);
assert_eq!(buf_32.len(), 32);
assert_eq!(buf_512.len(), 512);
assert_eq!(buf_mib.len(), 1 << 20);
Ok(())
}
#[test]
fn create_anonymous_buffers() -> Result<(), HardError> {
let buf_8_a = buffer!(8)?;
let buf_8_b = buffer!(8)?;
let buf_32 = buffer!(32)?;
let buf_512 = buffer!(512)?;
let buf_mib = buffer!(0x100000)?;
assert_eq!(buf_8_a.len(), 8);
assert_eq!(buf_8_b.len(), 8);
assert_eq!(buf_32.len(), 32);
assert_eq!(buf_512.len(), 512);
assert_eq!(buf_mib.len(), 1 << 20);
Ok(())
}
#[test]
#[cfg(feature = "restricted-types")]
fn buffer_traits_restricted() -> Result<(), HardError> {
let mut buf = buffer!(32)?;
buf.zero();
let buf_b = buf.try_clone()?;
assert_eq!(buf, buf_b);
let buf = buf.into_noaccess()?;
let buf = buf.into_mut()?;
let buf = buf.into_readonly()?;
let buf_c = buf.try_clone()?;
assert_eq!(buf, buf_c);
let buf = buf.into_noaccess()?;
let buf = buf.into_readonly()?;
let buf = buf.into_mut()?;
assert_eq!(*buf, [0; 32]);
Ok(())
}
#[test]
fn immutable_common_trait_impls() -> Result<(), HardError> {
let mut buf = buffer!(32)?;
buf.zero();
assert_eq!(buf.as_ref(), &[0; 32]);
let buf_ref: &[u8; 32] = buf.borrow();
assert_eq!(buf_ref, &[0; 32]);
format!("{:?}", buf);
assert_eq!(*buf, [0; 32]);
let mut other = buffer!(32)?;
other.zero();
assert_eq!(buf, other);
format!("{:p}", buf);
Ok(())
}
#[test]
fn mutable_common_trait_impls() -> Result<(), HardError> {
let mut buf = buffer!(32)?;
buf.zero();
assert_eq!(buf.as_mut(), &mut [0; 32]);
let buf_ref: &mut [u8; 32] = buf.borrow_mut();
assert_eq!(buf_ref, &mut [0; 32]);
assert_eq!(buf.deref_mut(), &mut [0; 32]);
Ok(())
}
}