#[cfg(feature = "page-seal")]
use core::mem::ManuallyDrop;
use core::{
fmt,
ptr::NonNull,
sync::atomic::{compiler_fence, Ordering},
};
#[cfg(feature = "canary-check")]
use core::cell::Cell;
use super::{
CanaryCorruptedError, ForkPolicy, ForkProtectionRequest, ProtectedSecretFillError,
ProtectionControl, ProtectionError, ProtectionFailure, ProtectionReport, ProtectionRequest,
ProtectionState, Requirement, RollbackReport, RollbackState, SecretIntegrityError,
};
#[cfg(all(
test,
feature = "page-seal",
target_os = "linux",
any(target_arch = "x86_64", target_arch = "aarch64"),
not(all(miri, test))
))]
unsafe extern "C" {
fn fork() -> i32;
fn waitpid(pid: i32, status: *mut i32, options: i32) -> i32;
fn _exit(status: i32) -> !;
}
#[cfg(all(
target_os = "linux",
any(target_arch = "x86_64", target_arch = "aarch64")
))]
use core::arch::asm;
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
use core::ffi::c_int;
#[cfg(not(target_os = "linux"))]
use core::ffi::c_void;
#[cfg(target_os = "windows")]
use core::mem::MaybeUninit;
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
const LINUX_PAGE_GRANULE: usize = 4096;
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
const UNIX_FALLBACK_PAGE_GRANULE: usize = 4096;
#[cfg(target_os = "windows")]
const WINDOWS_FALLBACK_PAGE_GRANULE: usize = 4096;
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
const PROT_NONE: usize = 0x0;
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
const PROT_READ: usize = 0x1;
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
const PROT_WRITE: usize = 0x2;
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
const MAP_PRIVATE: usize = 0x02;
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
const MAP_ANONYMOUS: usize = 0x1000;
#[cfg(target_os = "android")]
const MAP_ANONYMOUS: usize = 0x20;
#[cfg(all(feature = "memory-lock", target_os = "freebsd"))]
const MADV_NOCORE: i32 = 8;
#[cfg(target_os = "linux")]
const PROT_NONE: usize = 0x0;
#[cfg(target_os = "linux")]
const PROT_READ: usize = 0x1;
#[cfg(target_os = "linux")]
const PROT_WRITE: usize = 0x2;
#[cfg(target_os = "linux")]
const MAP_PRIVATE: usize = 0x02;
#[cfg(target_os = "linux")]
const MAP_ANONYMOUS: usize = 0x20;
#[cfg(target_os = "linux")]
const MAP_FD_ANONYMOUS: usize = (-1isize) as usize;
#[cfg(target_os = "linux")]
const MADV_DONTFORK: usize = 10;
#[cfg(feature = "memory-lock")]
#[cfg(target_os = "linux")]
const MADV_DONTDUMP: usize = 16;
#[cfg(target_os = "linux")]
const MADV_WIPEONFORK: usize = 18;
#[cfg(target_os = "windows")]
const MEM_COMMIT: u32 = 0x1000;
#[cfg(target_os = "windows")]
const MEM_RESERVE: u32 = 0x2000;
#[cfg(target_os = "windows")]
const MEM_RELEASE: u32 = 0x8000;
#[cfg(target_os = "windows")]
const PAGE_NOACCESS: u32 = 0x01;
#[cfg(target_os = "windows")]
const PAGE_READWRITE: u32 = 0x04;
#[cfg(feature = "canary-check")]
const CANARY_SIZE: usize = 8;
#[cfg(all(feature = "canary-check", not(feature = "random-canary")))]
const CANARY_MASK: u64 = 0xA11C_E5AF_EC0D_EC0D;
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
const SYS_MMAP: usize = 9;
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
const SYS_MPROTECT: usize = 10;
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
const SYS_MUNMAP: usize = 11;
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
const SYS_MADVISE: usize = 28;
#[cfg(all(feature = "memory-lock", target_os = "linux", target_arch = "x86_64"))]
const SYS_MLOCK: usize = 149;
#[cfg(all(feature = "memory-lock", target_os = "linux", target_arch = "x86_64"))]
const SYS_MUNLOCK: usize = 150;
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
const SYS_MMAP: usize = 222;
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
const SYS_MPROTECT: usize = 226;
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
const SYS_MUNMAP: usize = 215;
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
const SYS_MADVISE: usize = 233;
#[cfg(all(feature = "memory-lock", target_os = "linux", target_arch = "aarch64"))]
const SYS_MLOCK: usize = 228;
#[cfg(all(feature = "memory-lock", target_os = "linux", target_arch = "aarch64"))]
const SYS_MUNLOCK: usize = 229;
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
unsafe extern "C" {
fn getpagesize() -> i32;
fn mmap(
addr: *mut c_void,
len: usize,
prot: i32,
flags: i32,
fd: i32,
offset: isize,
) -> *mut c_void;
fn mprotect(addr: *mut c_void, len: usize, prot: i32) -> i32;
fn munmap(addr: *mut c_void, len: usize) -> i32;
#[cfg(all(feature = "memory-lock", target_os = "freebsd"))]
fn madvise(addr: *mut c_void, len: usize, advice: i32) -> i32;
#[cfg(feature = "memory-lock")]
fn mlock(addr: *const c_void, len: usize) -> i32;
#[cfg(feature = "memory-lock")]
fn munlock(addr: *const c_void, len: usize) -> i32;
#[cfg_attr(
any(target_os = "macos", target_os = "ios", target_os = "freebsd"),
link_name = "__error"
)]
#[cfg_attr(
any(target_os = "android", target_os = "openbsd", target_os = "netbsd"),
link_name = "__errno"
)]
#[cfg_attr(target_os = "dragonfly", link_name = "__errno_location")]
fn errno_location() -> *mut c_int;
}
#[cfg(target_os = "windows")]
#[repr(C)]
struct SystemInfo {
processor_architecture: u16,
reserved: u16,
page_size: u32,
minimum_application_address: *mut c_void,
maximum_application_address: *mut c_void,
active_processor_mask: usize,
number_of_processors: u32,
processor_type: u32,
allocation_granularity: u32,
processor_level: u16,
processor_revision: u16,
}
#[cfg(target_os = "windows")]
#[link(name = "kernel32")]
unsafe extern "system" {
fn GetLastError() -> u32;
fn GetSystemInfo(system_info: *mut SystemInfo);
fn VirtualAlloc(
address: *mut c_void,
size: usize,
allocation_type: u32,
protect: u32,
) -> *mut c_void;
fn VirtualFree(address: *mut c_void, size: usize, free_type: u32) -> i32;
fn VirtualProtect(
address: *mut c_void,
size: usize,
new_protect: u32,
old_protect: *mut u32,
) -> i32;
#[cfg(feature = "memory-lock")]
fn VirtualLock(address: *mut c_void, size: usize) -> i32;
#[cfg(feature = "memory-lock")]
fn VirtualUnlock(address: *mut c_void, size: usize) -> i32;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GuardPageOperation {
Length,
Map,
Protect,
DontDump,
DontFork,
WipeOnFork,
Lock,
Unlock,
Unmap,
Random,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct GuardPageError {
pub operation: GuardPageOperation,
pub errno: i32,
}
impl fmt::Display for GuardPageError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"guard page operation {:?} failed with errno {}",
self.operation, self.errno
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for GuardPageError {}
#[cfg(feature = "page-seal")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CleanupState {
NotNeeded,
Completed,
Failed(GuardPageError),
}
#[cfg(feature = "page-seal")]
impl CleanupState {
#[must_use]
#[inline]
pub const fn failure(self) -> Option<GuardPageError> {
match self {
Self::Failed(error) => Some(error),
Self::NotNeeded | Self::Completed => None,
}
}
}
#[cfg(feature = "page-seal")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CleanupReport {
pub normalization: CleanupState,
pub unlock: CleanupState,
pub unmap: CleanupState,
}
#[cfg(feature = "page-seal")]
impl CleanupReport {
#[must_use]
#[inline]
pub const fn completed(self) -> bool {
!self.normalization_failed() && !self.unlock_failed() && !self.unmap_failed()
}
#[must_use]
#[inline]
pub const fn normalization_failed(self) -> bool {
matches!(self.normalization, CleanupState::Failed(_))
}
#[must_use]
#[inline]
pub const fn unlock_failed(self) -> bool {
matches!(self.unlock, CleanupState::Failed(_))
}
#[must_use]
#[inline]
pub const fn unmap_failed(self) -> bool {
matches!(self.unmap, CleanupState::Failed(_))
}
#[must_use]
#[inline]
pub const fn first_failure(self) -> Option<GuardPageError> {
if let Some(error) = self.normalization.failure() {
return Some(error);
}
if let Some(error) = self.unlock.failure() {
return Some(error);
}
self.unmap.failure()
}
}
#[cfg(feature = "page-seal")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CleanupError {
failure: GuardPageError,
report: CleanupReport,
}
#[cfg(feature = "page-seal")]
impl CleanupError {
fn from_report(report: CleanupReport) -> Option<Self> {
report
.first_failure()
.map(|failure| Self { failure, report })
}
#[must_use]
#[inline]
pub const fn report(self) -> CleanupReport {
self.report
}
#[must_use]
#[inline]
pub const fn operation(self) -> GuardPageOperation {
self.failure.operation
}
#[must_use]
#[inline]
pub const fn errno(self) -> i32 {
self.failure.errno
}
}
#[cfg(feature = "page-seal")]
impl fmt::Display for CleanupError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "page-sealed cleanup failed: {}", self.failure)
}
}
#[cfg(all(feature = "page-seal", feature = "std"))]
impl std::error::Error for CleanupError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.failure)
}
}
impl From<GuardPageError> for SecretIntegrityError<GuardPageError> {
#[inline]
fn from(error: GuardPageError) -> Self {
Self::Operation(error)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GuardedSecretVecGenerateError<E> {
Guard(GuardPageError),
Generate(E),
}
impl<E: fmt::Display> fmt::Display for GuardedSecretVecGenerateError<E> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Guard(error) => error.fmt(formatter),
Self::Generate(error) => error.fmt(formatter),
}
}
}
#[cfg(feature = "std")]
impl<E> std::error::Error for GuardedSecretVecGenerateError<E>
where
E: std::error::Error + 'static,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Guard(error) => Some(error),
Self::Generate(error) => Some(error),
}
}
}
impl<E> From<GuardPageError> for GuardedSecretVecGenerateError<E> {
#[inline]
fn from(error: GuardPageError) -> Self {
Self::Guard(error)
}
}
pub struct GuardedSecretVec {
base: NonNull<u8>,
data: NonNull<u8>,
map_len: usize,
writable_len: usize,
data_capacity: usize,
len: usize,
locked: bool,
request: ProtectionRequest,
report: ProtectionReport,
#[cfg(feature = "canary-check")]
poisoned: Cell<bool>,
#[cfg(feature = "random-canary")]
canary: crate::canary::CanaryMaterial,
}
unsafe impl Send for GuardedSecretVec {}
impl GuardedSecretVec {
pub fn with_capacity(capacity: usize) -> Result<Self, GuardPageError> {
Self::with_capacity_with_protection(capacity, ProtectionRequest::guarded())
.map_err(protection_error_as_guard_page)
}
#[cfg(feature = "profile-guarded-native")]
#[inline]
pub fn with_capacity_guarded_native(capacity: usize) -> Result<Self, ProtectionError> {
Self::with_capacity_with_protection(capacity, ProtectionRequest::profile_guarded_native())
}
#[cfg(feature = "memory-lock")]
pub fn locked_with_capacity(capacity: usize) -> Result<Self, GuardPageError> {
Self::with_capacity_with_protection(capacity, ProtectionRequest::locked_guarded())
.map_err(protection_error_as_guard_page)
}
pub fn with_capacity_with_protection(
capacity: usize,
request: ProtectionRequest,
) -> Result<Self, ProtectionError> {
#[cfg(feature = "random-canary")]
let canary = random_canary_value().map_err(|error| {
guard_pre_mapping_error(request, capacity, ProtectionControl::Canary, error.errno)
})?;
let page_granule = platform_page_granule();
let mut report = ProtectionReport::pending(request, capacity, page_granule);
report.canary = resolve_guard_canary(request.canary, &report)?;
report.cache_policy = resolve_guard_unavailable(
request.cache_policy,
ProtectionControl::CachePolicy,
&report,
)?;
let data_capacity = guarded_payload_capacity(capacity).map_err(|error| {
guard_pre_mapping_error(request, capacity, ProtectionControl::Mapping, error.errno)
})?;
let writable_len = guarded_writable_len(data_capacity).map_err(|error| {
guard_pre_mapping_error(request, capacity, ProtectionControl::Mapping, error.errno)
})?;
let total_len = writable_len
.checked_add(page_granule)
.and_then(|value| value.checked_add(page_granule))
.ok_or_else(|| {
guard_pre_mapping_error(request, capacity, ProtectionControl::Mapping, 0)
})?;
let base = match map_guarded(total_len) {
Ok(base) => base,
Err(error) => {
report.mapping = ProtectionState::Failed { code: error.errno };
return Err(ProtectionError {
failure: ProtectionFailure {
control: ProtectionControl::Mapping,
code: error.errno,
},
partial_report: report,
rollback: RollbackReport::not_needed(),
});
}
};
report.mapping = ProtectionState::Established;
report.mapped_bytes = total_len;
let data_addr = match (base.as_ptr() as usize).checked_add(page_granule) {
Some(address) => address,
None => {
report.guard_pages = ProtectionState::Failed { code: 0 };
return Err(guard_required_error(
base,
total_len,
ProtectionControl::GuardPages,
0,
report,
));
}
};
let data = match NonNull::new(data_addr as *mut u8) {
Some(data) => data,
None => {
report.guard_pages = ProtectionState::Failed { code: 0 };
return Err(guard_required_error(
base,
total_len,
ProtectionControl::GuardPages,
0,
report,
));
}
};
if let Err(error) = protect_data(data, writable_len) {
report.guard_pages = ProtectionState::Failed { code: error.errno };
return Err(guard_required_error(
base,
total_len,
ProtectionControl::GuardPages,
error.errno,
report,
));
}
report.guard_pages = ProtectionState::Established;
report.dump_exclusion = apply_guard_control(
request.dump_exclusion,
dump_exclusion_supported(),
ProtectionControl::DumpExclusion,
&mut report,
base,
total_len,
data,
writable_len,
guard_mark_dontdump,
)?;
report.fork.state = apply_guard_fork_policy(
request.fork,
&mut report,
base,
total_len,
data,
writable_len,
)?;
report.memory_lock = apply_guard_control(
request.memory_lock,
cfg!(feature = "memory-lock"),
ProtectionControl::MemoryLock,
&mut report,
base,
total_len,
data,
writable_len,
guard_lock_mapping,
)?;
let locked = report.memory_lock == ProtectionState::Established;
if locked {
report.locked_bytes = writable_len;
}
let mut secret = Self {
base,
data,
map_len: total_len,
writable_len,
data_capacity,
len: 0,
locked,
request,
report,
#[cfg(feature = "canary-check")]
poisoned: Cell::new(false),
#[cfg(feature = "random-canary")]
canary,
};
secret.write_canaries();
Ok(secret)
}
pub fn from_slice_with_protection(
bytes: &[u8],
request: ProtectionRequest,
) -> Result<Self, ProtectedSecretFillError<core::convert::Infallible>> {
Self::try_from_exact_len_with_protection(bytes.len(), request, |output| {
output.copy_from_slice(bytes);
Ok::<(), core::convert::Infallible>(())
})
}
pub fn from_fn_with_protection(
len: usize,
request: ProtectionRequest,
mut make_byte: impl FnMut(usize) -> u8,
) -> Result<Self, ProtectedSecretFillError<core::convert::Infallible>> {
Self::try_from_exact_len_with_protection(len, request, |output| {
let mut index = 0;
while index < len {
output[index] = make_byte(index);
index += 1;
}
Ok::<(), core::convert::Infallible>(())
})
}
pub fn try_from_fn_with_protection<E>(
len: usize,
request: ProtectionRequest,
mut make_byte: impl FnMut(usize) -> Result<u8, E>,
) -> Result<Self, ProtectedSecretFillError<E>> {
Self::try_from_exact_len_with_protection(len, request, |output| {
let mut index = 0;
while index < len {
output[index] = make_byte(index)?;
index += 1;
}
Ok(())
})
}
pub fn from_exact_len_with_protection(
len: usize,
request: ProtectionRequest,
fill: impl FnOnce(&mut [u8]),
) -> Result<Self, ProtectedSecretFillError<core::convert::Infallible>> {
Self::try_from_exact_len_with_protection(len, request, |output| {
fill(output);
Ok::<(), core::convert::Infallible>(())
})
}
pub fn try_from_exact_len_with_protection<E>(
len: usize,
request: ProtectionRequest,
fill: impl FnOnce(&mut [u8]) -> Result<(), E>,
) -> Result<Self, ProtectedSecretFillError<E>> {
Self::try_from_capacity_with_protection(len, request, |output| {
fill(output)?;
Ok(len)
})
}
pub fn from_capacity_with_protection(
capacity: usize,
request: ProtectionRequest,
fill: impl FnOnce(&mut [u8]) -> usize,
) -> Result<Self, ProtectedSecretFillError<core::convert::Infallible>> {
Self::try_from_capacity_with_protection(capacity, request, |output| {
Ok::<usize, core::convert::Infallible>(fill(output))
})
}
pub fn try_from_capacity_bounded_with_protection<E>(
capacity: usize,
maximum: usize,
request: ProtectionRequest,
fill: impl FnOnce(&mut [u8]) -> Result<usize, E>,
) -> Result<Self, ProtectedSecretFillError<E>> {
if capacity > maximum {
return Err(ProtectedSecretFillError::CapacityLimit {
maximum,
actual: capacity,
});
}
Self::try_from_capacity_with_protection(capacity, request, fill)
}
pub fn try_from_capacity_with_protection<E>(
capacity: usize,
request: ProtectionRequest,
fill: impl FnOnce(&mut [u8]) -> Result<usize, E>,
) -> Result<Self, ProtectedSecretFillError<E>> {
Self::try_from_capacity_with_protection_inner(capacity, request, fill, |_| {})
}
#[inline]
fn try_from_capacity_with_protection_inner<E>(
capacity: usize,
request: ProtectionRequest,
fill: impl FnOnce(&mut [u8]) -> Result<usize, E>,
after_fill: impl FnOnce(&mut Self),
) -> Result<Self, ProtectedSecretFillError<E>> {
let mut secret = Self::with_capacity_with_protection(capacity, request)
.map_err(ProtectedSecretFillError::Protection)?;
{
let destination = secret.as_mut_capacity_slice();
crate::wipe_backend::erase(destination.as_mut_ptr(), destination.len());
}
secret.len = capacity;
secret.write_canaries();
compiler_fence(Ordering::SeqCst);
let fill_result = fill(&mut secret.as_mut_capacity_slice()[..capacity]);
after_fill(&mut secret);
secret
.verify_integrity()
.map_err(ProtectedSecretFillError::Integrity)?;
let len = match fill_result {
Ok(len) => len,
Err(error) => {
secret.clear_secret();
return Err(ProtectedSecretFillError::Fill(error));
}
};
if len > capacity {
secret.clear_secret();
return Err(ProtectedSecretFillError::Length(crate::LengthError {
expected: capacity,
actual: len,
}));
}
if len < secret.data_capacity {
let spare = &mut secret.as_mut_capacity_slice()[len..];
crate::wipe_backend::erase(spare.as_mut_ptr(), spare.len());
}
secret.finish_initialization(len);
Ok(secret)
}
#[cfg(all(test, feature = "canary-check", feature = "std"))]
pub(crate) fn try_from_capacity_with_protection_and_corrupt_boundary_for_test<E>(
capacity: usize,
request: ProtectionRequest,
fill: impl FnOnce(&mut [u8]) -> Result<usize, E>,
) -> Result<Self, ProtectedSecretFillError<E>> {
Self::try_from_capacity_with_protection_inner(capacity, request, fill, |secret| {
secret.corrupt_suffix_canary_for_test();
})
}
#[cfg(test)]
pub(crate) fn unreported_tail_is_clear_for_test(&self) -> bool {
#[cfg(feature = "canary-check")]
let tail_start = self.len.saturating_add(CANARY_SIZE).min(self.data_capacity);
#[cfg(not(feature = "canary-check"))]
let tail_start = self.len;
let mut index = tail_start;
while index < self.data_capacity {
if unsafe { core::ptr::read_volatile(self.payload_ptr().add(index)) } != 0 {
return false;
}
index += 1;
}
true
}
pub fn from_slice(bytes: &[u8]) -> Result<Self, GuardPageError> {
let mut secret = Self::with_capacity(bytes.len())?;
secret.as_mut_capacity_slice()[..bytes.len()].copy_from_slice(bytes);
secret.finish_initialization(bytes.len());
Ok(secret)
}
pub fn from_fn(
len: usize,
mut make_byte: impl FnMut(usize) -> u8,
) -> Result<Self, GuardPageError> {
let mut secret = Self::with_capacity(len)?;
secret.fill_from_fn(len, &mut make_byte);
Ok(secret)
}
pub fn try_from_fn<E>(
len: usize,
mut make_byte: impl FnMut(usize) -> Result<u8, E>,
) -> Result<Self, GuardedSecretVecGenerateError<E>> {
let mut secret = Self::with_capacity(len)?;
secret
.fill_from_try_fn(len, &mut make_byte)
.map_err(GuardedSecretVecGenerateError::Generate)?;
Ok(secret)
}
#[cfg(feature = "memory-lock")]
pub fn locked_from_slice(bytes: &[u8]) -> Result<Self, GuardPageError> {
let mut secret = Self::locked_with_capacity(bytes.len())?;
secret.as_mut_capacity_slice()[..bytes.len()].copy_from_slice(bytes);
secret.finish_initialization(bytes.len());
Ok(secret)
}
#[cfg(feature = "memory-lock")]
pub fn locked_from_fn(
len: usize,
mut make_byte: impl FnMut(usize) -> u8,
) -> Result<Self, GuardPageError> {
let mut secret = Self::locked_with_capacity(len)?;
secret.fill_from_fn(len, &mut make_byte);
Ok(secret)
}
#[cfg(feature = "memory-lock")]
pub fn locked_try_from_fn<E>(
len: usize,
mut make_byte: impl FnMut(usize) -> Result<u8, E>,
) -> Result<Self, GuardedSecretVecGenerateError<E>> {
let mut secret = Self::locked_with_capacity(len)?;
secret
.fill_from_try_fn(len, &mut make_byte)
.map_err(GuardedSecretVecGenerateError::Generate)?;
Ok(secret)
}
#[must_use]
#[inline]
pub const fn len(&self) -> usize {
self.len
}
#[must_use]
#[inline]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
#[inline]
pub const fn capacity(&self) -> usize {
self.data_capacity
}
#[must_use]
#[inline]
pub const fn is_memory_locked(&self) -> bool {
self.locked
}
#[must_use]
#[inline]
pub const fn protection_report(&self) -> &ProtectionReport {
&self.report
}
#[must_use]
#[inline]
pub const fn protection_request(&self) -> ProtectionRequest {
self.request
}
#[inline]
pub fn try_with_secret<R>(
&self,
inspect: impl FnOnce(&[u8]) -> R,
) -> Result<R, CanaryCorruptedError> {
self.verify_integrity()?;
let result = inspect(self.as_slice());
self.verify_integrity()?;
Ok(result)
}
#[inline]
pub fn try_with_secret_mut<R>(
&mut self,
edit: impl FnOnce(&mut [u8]) -> R,
) -> Result<R, CanaryCorruptedError> {
self.verify_integrity()?;
let result = edit(self.as_mut_slice());
compiler_fence(Ordering::SeqCst);
self.verify_integrity()?;
Ok(result)
}
pub fn try_extend_from_slice(
&mut self,
bytes: &[u8],
) -> Result<(), SecretIntegrityError<GuardPageError>> {
self.verify_integrity()?;
let required = self
.len
.checked_add(bytes.len())
.ok_or(SecretIntegrityError::Operation(GuardPageError {
operation: GuardPageOperation::Length,
errno: 0,
}))?;
if required > self.data_capacity {
self.grow_to(required)?;
}
let start = self.len;
let end = required;
self.as_mut_capacity_slice()[start..end].copy_from_slice(bytes);
self.finish_initialization(required);
Ok(())
}
pub fn try_replace_from_slice(
&mut self,
bytes: &[u8],
) -> Result<(), SecretIntegrityError<GuardPageError>> {
self.verify_integrity()?;
if bytes.len() > self.data_capacity {
let mut replacement = Self::with_capacity_with_protection(bytes.len(), self.request)
.map_err(protection_error_as_guard_page)
.map_err(SecretIntegrityError::Operation)?;
replacement.as_mut_capacity_slice()[..bytes.len()].copy_from_slice(bytes);
replacement.finish_initialization(bytes.len());
self.clear_secret();
core::mem::swap(self, &mut replacement);
return Ok(());
}
self.clear_secret();
self.as_mut_capacity_slice()[..bytes.len()].copy_from_slice(bytes);
self.finish_initialization(bytes.len());
Ok(())
}
pub fn try_replace_from_fn(
&mut self,
len: usize,
mut make_byte: impl FnMut(usize) -> u8,
) -> Result<(), SecretIntegrityError<GuardPageError>> {
self.verify_integrity()?;
let mut replacement = Self::with_capacity_with_protection(len, self.request)
.map_err(protection_error_as_guard_page)
.map_err(SecretIntegrityError::Operation)?;
replacement.fill_from_fn(len, &mut make_byte);
self.clear_secret();
core::mem::swap(self, &mut replacement);
Ok(())
}
pub fn try_replace_from_fallible_fn<E>(
&mut self,
len: usize,
mut make_byte: impl FnMut(usize) -> Result<u8, E>,
) -> Result<(), SecretIntegrityError<GuardedSecretVecGenerateError<E>>> {
self.verify_integrity()?;
let mut replacement = Self::with_capacity_with_protection(len, self.request)
.map_err(protection_error_as_guard_page)
.map_err(GuardedSecretVecGenerateError::Guard)
.map_err(SecretIntegrityError::Operation)?;
replacement
.fill_from_try_fn(len, &mut make_byte)
.map_err(GuardedSecretVecGenerateError::Generate)
.map_err(SecretIntegrityError::Operation)?;
self.clear_secret();
core::mem::swap(self, &mut replacement);
Ok(())
}
#[inline(never)]
pub fn clear_secret(&mut self) {
crate::wipe_backend::erase(self.data.as_ptr(), self.writable_len);
self.len = 0;
self.write_canaries();
}
#[inline]
pub fn into_cleared(mut self) {
self.clear_secret();
}
#[cfg(feature = "cache-flush")]
#[inline(never)]
pub fn try_clear_secret_and_flush(
&mut self,
) -> Result<crate::cache_flush::CacheFlushReport, crate::cache_flush::CacheFlushError> {
self.clear_secret();
crate::cache_flush::flush_cache_lines(self.as_capacity_slice())
}
#[inline]
pub fn try_constant_time_eq(&self, other: &[u8]) -> Result<bool, CanaryCorruptedError> {
self.verify_integrity()?;
Ok(crate::constant_time_eq_slices(self.as_slice(), other))
}
#[inline]
pub fn verify_integrity(&self) -> Result<(), CanaryCorruptedError> {
#[cfg(not(feature = "canary-check"))]
{
Ok(())
}
#[cfg(feature = "canary-check")]
{
if !self.poisoned.get() && self.canaries_intact() {
Ok(())
} else {
self.clear_after_canary_failure();
Err(CanaryCorruptedError)
}
}
}
fn grow_to(&mut self, required: usize) -> Result<(), SecretIntegrityError<GuardPageError>> {
self.verify_integrity()?;
let page_granule = platform_page_granule();
let next_capacity = self
.data_capacity
.saturating_mul(2)
.max(required)
.max(page_granule);
let mut replacement = Self::with_capacity_with_protection(next_capacity, self.request)
.map_err(protection_error_as_guard_page)
.map_err(SecretIntegrityError::Operation)?;
replacement.as_mut_capacity_slice()[..self.len].copy_from_slice(self.as_slice());
replacement.finish_initialization(self.len);
self.clear_secret();
core::mem::swap(self, &mut replacement);
Ok(())
}
#[inline]
pub fn with_secret_or_panic<R>(&self, inspect: impl FnOnce(&[u8]) -> R) -> R {
self.try_with_secret(inspect)
.unwrap_or_else(|_| panic!("guarded secret canary corrupted"))
}
#[inline]
pub fn with_secret_mut_or_panic<R>(&mut self, edit: impl FnOnce(&mut [u8]) -> R) -> R {
self.try_with_secret_mut(edit)
.unwrap_or_else(|_| panic!("guarded secret canary corrupted"))
}
#[must_use]
#[inline]
pub fn constant_time_eq_or_panic(&self, other: &[u8]) -> bool {
self.try_constant_time_eq(other)
.unwrap_or_else(|_| panic!("guarded secret canary corrupted"))
}
fn fill_from_fn(&mut self, len: usize, make_byte: &mut impl FnMut(usize) -> u8) {
assert!(
len <= self.data_capacity,
"guarded secret length exceeds capacity"
);
compiler_fence(Ordering::SeqCst);
let capacity = self.as_mut_capacity_slice();
let mut index = 0;
while index < len {
capacity[index] = make_byte(index);
index += 1;
}
self.finish_initialization(len);
}
fn fill_from_try_fn<E>(
&mut self,
len: usize,
make_byte: &mut impl FnMut(usize) -> Result<u8, E>,
) -> Result<(), E> {
assert!(
len <= self.data_capacity,
"guarded secret length exceeds capacity"
);
compiler_fence(Ordering::SeqCst);
let mut index = 0;
while index < len {
let byte = match make_byte(index) {
Ok(byte) => byte,
Err(error) => {
self.clear_secret();
return Err(error);
}
};
self.as_mut_capacity_slice()[index] = byte;
index += 1;
}
self.finish_initialization(len);
Ok(())
}
#[inline]
fn finish_initialization(&mut self, len: usize) {
assert!(
len <= self.data_capacity,
"guarded secret length exceeds capacity"
);
self.len = len;
self.write_canaries();
compiler_fence(Ordering::SeqCst);
}
#[inline]
fn as_slice(&self) -> &[u8] {
unsafe { core::slice::from_raw_parts(self.payload_ptr(), self.len) }
}
#[inline]
fn as_mut_slice(&mut self) -> &mut [u8] {
unsafe { core::slice::from_raw_parts_mut(self.payload_ptr(), self.len) }
}
#[inline]
fn as_mut_capacity_slice(&mut self) -> &mut [u8] {
unsafe { core::slice::from_raw_parts_mut(self.payload_ptr(), self.data_capacity) }
}
#[cfg(feature = "cache-flush")]
#[inline]
fn as_capacity_slice(&self) -> &[u8] {
unsafe { core::slice::from_raw_parts(self.data.as_ptr(), self.writable_len) }
}
#[inline]
fn payload_ptr(&self) -> *mut u8 {
unsafe { self.data.as_ptr().add(Self::payload_offset()) }
}
#[cfg(feature = "canary-check")]
#[inline]
const fn payload_offset() -> usize {
CANARY_SIZE
}
#[cfg(not(feature = "canary-check"))]
#[inline]
const fn payload_offset() -> usize {
0
}
#[cfg(all(feature = "canary-check", not(feature = "random-canary")))]
#[inline]
fn canary_value(&self) -> [u8; CANARY_SIZE] {
((self.data.as_ptr() as u64) ^ CANARY_MASK).to_ne_bytes()
}
#[cfg(feature = "random-canary")]
#[inline]
fn with_canary<R>(&self, use_canary: impl FnOnce(&[u8; CANARY_SIZE]) -> R) -> R {
use_canary(self.canary.as_bytes())
}
#[cfg(all(feature = "canary-check", not(feature = "random-canary")))]
#[inline]
fn with_canary<R>(&self, use_canary: impl FnOnce(&[u8; CANARY_SIZE]) -> R) -> R {
let canary = self.canary_value();
use_canary(&canary)
}
#[cfg(feature = "canary-check")]
#[inline]
fn canaries_intact(&self) -> bool {
let prefix = unsafe { core::slice::from_raw_parts(self.data.as_ptr(), CANARY_SIZE) };
let suffix = unsafe {
core::slice::from_raw_parts(self.data.as_ptr().add(CANARY_SIZE + self.len), CANARY_SIZE)
};
self.with_canary(|expected| {
crate::constant_time_eq_slices(prefix, expected)
& crate::constant_time_eq_slices(suffix, expected)
})
}
#[cfg(feature = "canary-check")]
#[inline]
fn write_canaries(&mut self) {
self.with_canary(|canary| {
unsafe {
core::ptr::copy_nonoverlapping(canary.as_ptr(), self.data.as_ptr(), CANARY_SIZE);
core::ptr::copy_nonoverlapping(
canary.as_ptr(),
self.data.as_ptr().add(CANARY_SIZE + self.len),
CANARY_SIZE,
);
}
});
compiler_fence(Ordering::SeqCst);
}
#[cfg(not(feature = "canary-check"))]
#[inline]
fn write_canaries(&mut self) {}
#[cfg(feature = "canary-check")]
#[inline]
fn clear_after_canary_failure(&self) {
self.poisoned.set(true);
crate::wipe_backend::erase(self.data.as_ptr(), self.writable_len);
}
#[cfg(feature = "random-canary")]
#[inline]
fn clear_canary_material(&mut self) {
self.canary.clear();
}
#[cfg(feature = "page-seal")]
#[inline]
fn mark_memory_unlocked(&mut self) {
self.locked = false;
self.report.memory_lock = ProtectionState::NotApplicable;
self.report.locked_bytes = 0;
}
#[cfg(feature = "page-seal")]
#[inline]
fn mark_mapping_released(&mut self) {
self.mark_memory_unlocked();
self.report.mapping = ProtectionState::NotApplicable;
self.report.dump_exclusion = ProtectionState::NotApplicable;
self.report.fork.state = ProtectionState::NotApplicable;
self.report.guard_pages = ProtectionState::NotApplicable;
self.report.canary = ProtectionState::NotApplicable;
self.report.cache_policy = ProtectionState::NotApplicable;
self.report.mapped_bytes = 0;
}
#[cfg(all(test, feature = "canary-check", feature = "std"))]
#[allow(dead_code)]
#[inline]
pub(crate) fn corrupt_suffix_canary_for_test(&mut self) {
unsafe {
let byte = self.data.as_ptr().add(CANARY_SIZE + self.len);
core::ptr::write(byte, core::ptr::read(byte) ^ 0xFF);
}
}
}
impl Drop for GuardedSecretVec {
#[inline]
fn drop(&mut self) {
self.clear_secret();
#[cfg(feature = "random-canary")]
self.clear_canary_material();
#[cfg(feature = "memory-lock")]
if self.locked {
let _ = unlock_mapping(self.data, self.writable_len);
}
let _ = unmap_guarded(self.base, self.map_len);
}
}
#[cfg(feature = "cache-flush")]
impl crate::cache_flush::CacheFlushSanitize for GuardedSecretVec {
#[inline(never)]
fn cache_flush_sanitize(
&mut self,
) -> Result<crate::cache_flush::CacheFlushReport, crate::cache_flush::CacheFlushError> {
self.try_clear_secret_and_flush()
}
}
impl crate::SecureSanitize for GuardedSecretVec {
#[inline]
fn secure_sanitize(&mut self) {
self.clear_secret();
}
}
impl fmt::Debug for GuardedSecretVec {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("GuardedSecretVec")
.field("len", &self.len)
.field("capacity", &self.data_capacity)
.field("writable_len", &self.writable_len)
.field("memory_locked", &self.locked)
.field("contents", &"<redacted>")
.finish()
}
}
#[cfg(feature = "page-seal")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SealedSecretAccessError {
Guard(GuardPageError),
Canary(CanaryCorruptedError),
AccessInProgress,
Retired,
Poisoned,
}
#[cfg(feature = "page-seal")]
impl fmt::Display for SealedSecretAccessError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Guard(error) => error.fmt(formatter),
Self::Canary(error) => error.fmt(formatter),
Self::AccessInProgress => {
formatter.write_str("page-sealed secret access is already in progress")
}
Self::Retired => formatter.write_str("page-sealed secret mapping is retired"),
Self::Poisoned => formatter.write_str("page-sealed secret mapping is poisoned"),
}
}
}
#[cfg(all(feature = "page-seal", feature = "std"))]
impl std::error::Error for SealedSecretAccessError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Guard(error) => Some(error),
Self::Canary(error) => Some(error),
Self::AccessInProgress | Self::Retired | Self::Poisoned => None,
}
}
}
#[cfg(feature = "page-seal")]
impl From<GuardPageError> for SealedSecretAccessError {
#[inline]
fn from(error: GuardPageError) -> Self {
Self::Guard(error)
}
}
#[cfg(feature = "page-seal")]
impl From<CanaryCorruptedError> for SealedSecretAccessError {
#[inline]
fn from(error: CanaryCorruptedError) -> Self {
Self::Canary(error)
}
}
#[cfg(feature = "page-seal")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum SealedState {
Sealed,
Exposed,
Poisoned,
Retired,
}
#[cfg(feature = "page-seal")]
pub struct SealedSecretBytes<const N: usize> {
inner: ManuallyDrop<GuardedSecretVec>,
state: SealedState,
#[cfg(test)]
fail_next_seal: bool,
#[cfg(test)]
fail_next_unseal: bool,
#[cfg(test)]
fail_normalization_page: Option<usize>,
#[cfg(test)]
fail_cleanup_reseal_page: Option<usize>,
#[cfg(test)]
cleanup_reseal_failed_page: Option<usize>,
#[cfg(test)]
fail_next_unmap: bool,
}
#[cfg(feature = "page-seal")]
unsafe impl<const N: usize> Send for SealedSecretBytes<N> {}
#[cfg(feature = "page-seal")]
impl<const N: usize> SealedSecretBytes<N> {
pub fn zeroed() -> Result<Self, GuardPageError> {
Self::zeroed_with_protection(ProtectionRequest::page_sealed())
.map_err(protection_error_as_guard_page)
}
pub fn zeroed_with_protection(request: ProtectionRequest) -> Result<Self, ProtectionError> {
let mut inner = GuardedSecretVec::with_capacity_with_protection(N, request)?;
inner.finish_initialization(N);
if let Err(error) = seal_data(inner.data, inner.writable_len) {
let mut report = *inner.protection_report();
report.guard_pages = ProtectionState::Failed { code: error.errno };
let rollback = rollback_sealed_transition_failure(inner);
return Err(ProtectionError {
failure: ProtectionFailure {
control: ProtectionControl::GuardPages,
code: error.errno,
},
partial_report: report,
rollback,
});
}
Ok(Self {
inner: ManuallyDrop::new(inner),
state: SealedState::Sealed,
#[cfg(test)]
fail_next_seal: false,
#[cfg(test)]
fail_next_unseal: false,
#[cfg(test)]
fail_normalization_page: None,
#[cfg(test)]
fail_cleanup_reseal_page: None,
#[cfg(test)]
cleanup_reseal_failed_page: None,
#[cfg(test)]
fail_next_unmap: false,
})
}
pub fn from_array(mut bytes: [u8; N]) -> Result<Self, SealedSecretAccessError> {
let result = Self::zeroed()
.map_err(SealedSecretAccessError::Guard)
.and_then(|mut secret| {
secret.try_with_secret_mut(|target| target.copy_from_slice(&bytes))?;
Ok(secret)
});
crate::wipe::bytes(&mut bytes);
result
}
#[must_use]
#[inline]
pub const fn len(&self) -> usize {
N
}
#[must_use]
#[inline]
pub const fn is_empty(&self) -> bool {
N == 0
}
#[must_use]
#[inline]
pub const fn is_sealed(&self) -> bool {
matches!(self.state, SealedState::Sealed)
}
#[must_use]
#[inline]
pub const fn is_retired(&self) -> bool {
matches!(self.state, SealedState::Retired)
}
#[must_use]
#[inline]
pub const fn is_poisoned(&self) -> bool {
matches!(self.state, SealedState::Poisoned)
}
#[must_use]
#[inline]
pub fn protection_report(&self) -> &ProtectionReport {
self.inner.protection_report()
}
#[must_use]
#[inline]
pub fn protection_request(&self) -> ProtectionRequest {
self.inner.protection_request()
}
pub fn try_with_secret<R>(
&mut self,
inspect: impl FnOnce(&[u8; N]) -> R,
) -> Result<R, SealedSecretAccessError> {
self.begin_access()?;
let guard = SealedAccessGuard::new(self);
if let Err(error) = guard.secret.inner.verify_integrity() {
guard.secret.reset_zeroed_payload();
guard.finish()?;
return Err(error.into());
}
let bytes = unsafe { &*(guard.secret.inner.payload_ptr() as *const [u8; N]) };
let result = inspect(bytes);
guard.finish()?;
Ok(result)
}
pub fn try_with_secret_mut<R>(
&mut self,
edit: impl FnOnce(&mut [u8; N]) -> R,
) -> Result<R, SealedSecretAccessError> {
self.begin_access()?;
let guard = SealedAccessGuard::new(self);
if let Err(error) = guard.secret.inner.verify_integrity() {
guard.secret.reset_zeroed_payload();
guard.finish()?;
return Err(error.into());
}
let bytes = unsafe { &mut *(guard.secret.inner.payload_ptr() as *mut [u8; N]) };
let result = edit(bytes);
compiler_fence(Ordering::SeqCst);
guard.finish()?;
Ok(result)
}
pub fn try_constant_time_eq(
&mut self,
other: &[u8; N],
) -> Result<bool, SealedSecretAccessError> {
self.try_with_secret(|bytes| crate::constant_time_eq_slices(bytes, other))
}
#[inline]
pub fn with_secret_or_panic<R>(&mut self, inspect: impl FnOnce(&[u8; N]) -> R) -> R {
self.try_with_secret(inspect)
.unwrap_or_else(|_| panic!("sealed secret access failed"))
}
#[inline]
pub fn with_secret_mut_or_panic<R>(&mut self, edit: impl FnOnce(&mut [u8; N]) -> R) -> R {
self.try_with_secret_mut(edit)
.unwrap_or_else(|_| panic!("sealed secret mutation failed"))
}
#[must_use]
#[inline]
pub fn constant_time_eq_or_panic(&mut self, other: &[u8; N]) -> bool {
self.try_constant_time_eq(other)
.unwrap_or_else(|_| panic!("sealed secret comparison failed"))
}
pub fn try_clear_secret(&mut self) -> Result<(), SealedSecretAccessError> {
self.begin_access()?;
let guard = SealedAccessGuard::new(self);
guard.secret.reset_zeroed_payload();
guard.finish()
}
#[inline]
pub fn clear_secret_or_panic(&mut self) {
self.try_clear_secret()
.unwrap_or_else(|_| panic!("sealed secret clear failed"));
}
#[inline]
pub fn try_secure_sanitize(&mut self) -> Result<(), SealedSecretAccessError> {
self.try_clear_secret()
}
pub fn try_close(&mut self) -> Result<(), CleanupError> {
if self.state == SealedState::Retired {
return Ok(());
}
let report = self.cleanup_mapping();
if report.completed() {
Ok(())
} else {
match CleanupError::from_report(report) {
Some(error) => Err(error),
None => Ok(()),
}
}
}
fn begin_access(&mut self) -> Result<(), SealedSecretAccessError> {
match self.state {
SealedState::Sealed => {}
SealedState::Exposed => return Err(SealedSecretAccessError::AccessInProgress),
SealedState::Poisoned => return Err(SealedSecretAccessError::Poisoned),
SealedState::Retired => return Err(SealedSecretAccessError::Retired),
}
#[cfg(test)]
if core::mem::take(&mut self.fail_next_unseal) {
let error = match simulate_partial_transition(
self.inner.data,
self.inner.writable_len,
PageProtection::ReadWrite,
) {
Ok(()) => GuardPageError {
operation: GuardPageOperation::Protect,
errno: 0,
},
Err(error) => error,
};
self.retire_after_transition_failure();
return Err(error.into());
}
if let Err(error) = protect_data(self.inner.data, self.inner.writable_len) {
self.retire_after_transition_failure();
return Err(error.into());
}
self.state = SealedState::Exposed;
Ok(())
}
fn finish_access(&mut self) -> Result<(), SealedSecretAccessError> {
if self.state != SealedState::Exposed {
return match self.state {
SealedState::Sealed | SealedState::Exposed => {
Err(SealedSecretAccessError::AccessInProgress)
}
SealedState::Poisoned => Err(SealedSecretAccessError::Poisoned),
SealedState::Retired => Err(SealedSecretAccessError::Retired),
};
}
let integrity_result = self.inner.verify_integrity().map_err(Into::into);
#[cfg(test)]
let seal_result = if core::mem::take(&mut self.fail_next_seal) {
match simulate_partial_transition(
self.inner.data,
self.inner.writable_len,
PageProtection::NoAccess,
) {
Ok(()) => Err(GuardPageError {
operation: GuardPageOperation::Protect,
errno: 0,
}),
Err(error) => Err(error),
}
} else {
seal_data(self.inner.data, self.inner.writable_len)
};
#[cfg(not(test))]
let seal_result = seal_data(self.inner.data, self.inner.writable_len);
match seal_result {
Ok(()) => {
self.state = SealedState::Sealed;
integrity_result
}
Err(error) => {
self.retire_after_transition_failure();
Err(error.into())
}
}
}
fn reset_zeroed_payload(&mut self) {
crate::wipe_backend::erase(self.inner.data.as_ptr(), self.inner.writable_len);
self.inner.len = N;
self.inner.write_canaries();
}
fn retire_after_transition_failure(&mut self) {
let _ = self.cleanup_mapping();
}
fn cleanup_mapping(&mut self) -> CleanupReport {
if self.state == SealedState::Retired {
return CleanupReport {
normalization: CleanupState::NotNeeded,
unlock: CleanupState::NotNeeded,
unmap: CleanupState::NotNeeded,
};
}
self.state = SealedState::Poisoned;
#[cfg(test)]
let normalization = {
let failed_write_page = self.fail_normalization_page.take();
let failed_reseal_page = self.fail_cleanup_reseal_page.take();
self.cleanup_reseal_failed_page = None;
let (result, observed_reseal_failure) = match (failed_write_page, failed_reseal_page) {
(None, None) => (
erase_and_reseal_data_pages(self.inner.data, self.inner.writable_len),
None,
),
(failed_write_page, failed_reseal_page) => {
erase_and_reseal_data_pages_with_failures(
self.inner.data,
self.inner.writable_len,
failed_write_page,
failed_reseal_page,
)
}
};
if result.is_err() {
self.cleanup_reseal_failed_page = observed_reseal_failure;
}
result
};
#[cfg(not(test))]
let normalization = erase_and_reseal_data_pages(self.inner.data, self.inner.writable_len);
let normalization = match normalization {
Ok(()) => CleanupState::Completed,
Err(error) => {
return CleanupReport {
normalization: CleanupState::Failed(error),
unlock: CleanupState::NotNeeded,
unmap: CleanupState::NotNeeded,
};
}
};
#[cfg(feature = "random-canary")]
self.inner.clear_canary_material();
#[cfg(test)]
let unmap = if core::mem::take(&mut self.fail_next_unmap) {
Err(GuardPageError {
operation: GuardPageOperation::Unmap,
errno: 0,
})
} else {
unmap_guarded(self.inner.base, self.inner.map_len)
};
#[cfg(not(test))]
let unmap = unmap_guarded(self.inner.base, self.inner.map_len);
#[cfg(feature = "memory-lock")]
let unlock = if unmap.is_ok() || !self.inner.locked {
CleanupState::NotNeeded
} else {
match unlock_mapping(self.inner.data, self.inner.writable_len) {
Ok(()) => {
self.inner.mark_memory_unlocked();
CleanupState::Completed
}
Err(error) => CleanupState::Failed(error),
}
};
#[cfg(not(feature = "memory-lock"))]
let unlock = CleanupState::NotNeeded;
if unmap.is_ok() {
self.state = SealedState::Retired;
self.inner.mark_mapping_released();
}
CleanupReport {
normalization,
unlock,
unmap: match unmap {
Ok(()) => CleanupState::Completed,
Err(error) => CleanupState::Failed(error),
},
}
}
#[cfg(all(
test,
target_os = "linux",
any(target_arch = "x86_64", target_arch = "aarch64"),
not(miri)
))]
pub(crate) fn fail_next_seal_for_test(&mut self) {
self.fail_next_seal = true;
}
#[cfg(all(
test,
target_os = "linux",
any(target_arch = "x86_64", target_arch = "aarch64"),
not(miri)
))]
pub(crate) fn fail_next_unseal_for_test(&mut self) {
self.fail_next_unseal = true;
}
#[cfg(all(
test,
target_os = "linux",
any(target_arch = "x86_64", target_arch = "aarch64"),
not(miri)
))]
pub(crate) fn fail_normalization_page_for_test(&mut self, page_index: usize) {
self.fail_normalization_page = Some(page_index);
}
#[cfg(all(
test,
target_os = "linux",
any(target_arch = "x86_64", target_arch = "aarch64"),
not(miri)
))]
pub(crate) fn fail_cleanup_reseal_page_for_test(&mut self, page_index: usize) {
self.fail_cleanup_reseal_page = Some(page_index);
}
#[cfg(all(
test,
target_os = "linux",
any(target_arch = "x86_64", target_arch = "aarch64"),
not(miri)
))]
pub(crate) fn fail_next_unmap_for_test(&mut self) {
self.fail_next_unmap = true;
}
#[cfg(all(
test,
target_os = "linux",
any(target_arch = "x86_64", target_arch = "aarch64"),
not(miri)
))]
pub(crate) fn erased_page_is_zero_for_test(
&mut self,
page_index: usize,
) -> Result<bool, GuardPageError> {
let page_granule = platform_page_granule();
let offset = page_index
.checked_mul(page_granule)
.filter(|offset| *offset < self.inner.writable_len)
.ok_or(GuardPageError {
operation: GuardPageOperation::Length,
errno: 0,
})?;
let page = unsafe { NonNull::new_unchecked(self.inner.data.as_ptr().add(offset)) };
protect_data(page, page_granule)?;
let erased = unsafe { core::slice::from_raw_parts(page.as_ptr(), page_granule) }
.iter()
.all(|byte| *byte == 0);
seal_data(page, page_granule)?;
if self.cleanup_reseal_failed_page == Some(page_index) {
self.cleanup_reseal_failed_page = None;
}
Ok(erased)
}
#[cfg(all(
test,
target_os = "linux",
any(target_arch = "x86_64", target_arch = "aarch64"),
not(miri)
))]
pub(crate) unsafe fn writable_failed_reseal_page_is_zero_for_test(
&self,
page_index: usize,
) -> Result<bool, GuardPageError> {
if self.state != SealedState::Poisoned
|| self.cleanup_reseal_failed_page != Some(page_index)
{
return Err(GuardPageError {
operation: GuardPageOperation::Protect,
errno: 0,
});
}
let page_granule = platform_page_granule();
let offset = page_index
.checked_mul(page_granule)
.filter(|offset| *offset < self.inner.writable_len)
.ok_or(GuardPageError {
operation: GuardPageOperation::Length,
errno: 0,
})?;
let page = unsafe { self.inner.data.as_ptr().add(offset) };
Ok(unsafe { core::slice::from_raw_parts(page, page_granule) }
.iter()
.all(|byte| *byte == 0))
}
#[cfg(all(
test,
target_os = "linux",
any(target_arch = "x86_64", target_arch = "aarch64"),
not(miri)
))]
pub(crate) fn mark_access_in_progress_for_test(&mut self) -> Result<(), GuardPageError> {
protect_data(self.inner.data, self.inner.writable_len)?;
self.state = SealedState::Exposed;
Ok(())
}
#[cfg(all(
test,
target_os = "linux",
any(target_arch = "x86_64", target_arch = "aarch64"),
not(miri)
))]
pub(crate) fn child_observes_zero_during_exposed_fork_for_test(
&mut self,
) -> Result<bool, SealedSecretAccessError> {
self.begin_access()?;
let guard = SealedAccessGuard::new(self);
let pid = unsafe { fork() };
if pid == 0 {
let bytes = unsafe {
core::slice::from_raw_parts(guard.secret.inner.payload_ptr() as *const u8, N)
};
let all_zero = bytes.iter().all(|byte| *byte == 0);
unsafe { _exit(if all_zero { 0 } else { 1 }) };
}
let mut status = -1;
let waited = if pid > 0 {
unsafe { waitpid(pid, &mut status, 0) }
} else {
-1
};
guard.finish()?;
Ok(waited == pid && status == 0)
}
#[cfg(all(
test,
feature = "canary-check",
feature = "std",
target_os = "linux",
any(target_arch = "x86_64", target_arch = "aarch64"),
not(miri)
))]
pub(crate) fn corrupt_canary_for_test(&mut self) -> Result<(), SealedSecretAccessError> {
self.begin_access()?;
let guard = SealedAccessGuard::new(self);
guard.secret.inner.corrupt_suffix_canary_for_test();
guard.finish()
}
}
#[cfg(feature = "page-seal")]
struct SealedAccessGuard<'a, const N: usize> {
secret: &'a mut SealedSecretBytes<N>,
active: bool,
}
#[cfg(feature = "page-seal")]
impl<'a, const N: usize> SealedAccessGuard<'a, N> {
fn new(secret: &'a mut SealedSecretBytes<N>) -> Self {
Self {
secret,
active: true,
}
}
fn finish(mut self) -> Result<(), SealedSecretAccessError> {
let result = self.secret.finish_access();
self.active = false;
result
}
}
#[cfg(feature = "page-seal")]
impl<const N: usize> Drop for SealedAccessGuard<'_, N> {
fn drop(&mut self) {
if self.active {
let _ = self.secret.finish_access();
}
}
}
#[cfg(feature = "page-seal")]
impl<const N: usize> Drop for SealedSecretBytes<N> {
fn drop(&mut self) {
let _ = self.cleanup_mapping();
}
}
#[cfg(feature = "page-seal")]
impl<const N: usize> fmt::Debug for SealedSecretBytes<N> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SealedSecretBytes")
.field("len", &N)
.field("sealed", &self.is_sealed())
.field("poisoned", &self.is_poisoned())
.field("retired", &self.is_retired())
.field("contents", &"<redacted>")
.finish()
}
}
#[cfg(feature = "page-seal")]
fn rollback_sealed_transition_failure(mut inner: GuardedSecretVec) -> RollbackReport {
if normalize_data_pages(inner.data, inner.writable_len, PageProtection::ReadWrite).is_ok() {
inner.clear_secret();
}
#[cfg(feature = "random-canary")]
inner.clear_canary_material();
let inner = ManuallyDrop::new(inner);
#[cfg(feature = "memory-lock")]
let unlock = if inner.locked {
match unlock_mapping(inner.data, inner.writable_len) {
Ok(()) => RollbackState::Completed,
Err(error) => RollbackState::Failed(ProtectionFailure {
control: ProtectionControl::MemoryLock,
code: error.errno,
}),
}
} else {
RollbackState::NotNeeded
};
#[cfg(not(feature = "memory-lock"))]
let unlock = RollbackState::NotNeeded;
let unmap = match unmap_guarded(inner.base, inner.map_len) {
Ok(()) => RollbackState::Completed,
Err(error) => RollbackState::Failed(ProtectionFailure {
control: ProtectionControl::Mapping,
code: error.errno,
}),
};
RollbackReport { unlock, unmap }
}
fn resolve_guard_unavailable(
requirement: Requirement,
control: ProtectionControl,
report: &ProtectionReport,
) -> Result<ProtectionState, ProtectionError> {
match super::protection::unavailable_state(requirement) {
Ok(state) => Ok(state),
Err(()) => Err(ProtectionError {
failure: ProtectionFailure { control, code: 0 },
partial_report: *report,
rollback: RollbackReport::not_needed(),
}),
}
}
fn resolve_guard_canary(
requirement: Requirement,
report: &ProtectionReport,
) -> Result<ProtectionState, ProtectionError> {
#[cfg(feature = "canary-check")]
{
let _ = requirement;
let _ = report;
Ok(ProtectionState::Established)
}
#[cfg(not(feature = "canary-check"))]
{
resolve_guard_unavailable(requirement, ProtectionControl::Canary, report)
}
}
fn guard_pre_mapping_error(
request: ProtectionRequest,
requested_bytes: usize,
control: ProtectionControl,
code: i32,
) -> ProtectionError {
let mut report = ProtectionReport::pending(request, requested_bytes, platform_page_granule());
set_guard_failed_state(&mut report, control, code);
ProtectionError {
failure: ProtectionFailure { control, code },
partial_report: report,
rollback: RollbackReport::not_needed(),
}
}
#[allow(clippy::too_many_arguments)]
fn apply_guard_fork_policy(
request: ForkProtectionRequest,
report: &mut ProtectionReport,
base: NonNull<u8>,
total_len: usize,
data: NonNull<u8>,
writable_len: usize,
) -> Result<ProtectionState, ProtectionError> {
match request.policy {
ForkPolicy::Inherit => Ok(ProtectionState::Established),
ForkPolicy::Exclude => apply_guard_control(
request.requirement,
fork_exclusion_supported(),
ProtectionControl::ForkPolicy,
report,
base,
total_len,
data,
writable_len,
guard_mark_dontfork,
),
ForkPolicy::WipeChild => apply_guard_control(
request.requirement,
wipe_child_supported(),
ProtectionControl::ForkPolicy,
report,
base,
total_len,
data,
writable_len,
guard_mark_wipeonfork,
),
}
}
#[allow(clippy::too_many_arguments)]
fn apply_guard_control(
requirement: Requirement,
supported: bool,
control: ProtectionControl,
report: &mut ProtectionReport,
base: NonNull<u8>,
total_len: usize,
data: NonNull<u8>,
writable_len: usize,
apply: fn(NonNull<u8>, usize) -> Result<(), GuardPageError>,
) -> Result<ProtectionState, ProtectionError> {
if requirement == Requirement::NotRequested {
return Ok(ProtectionState::NotRequested);
}
if !supported {
if requirement == Requirement::Preferred {
return Ok(ProtectionState::Unsupported);
}
set_guard_failed_state(report, control, 0);
return Err(guard_required_error(base, total_len, control, 0, *report));
}
match apply(data, writable_len) {
Ok(()) => Ok(ProtectionState::Established),
Err(error) => {
if control == ProtectionControl::MemoryLock {
report.lock_quota_likely = lock_quota_likely(error.errno);
}
if requirement == Requirement::Preferred {
return Ok(ProtectionState::Failed { code: error.errno });
}
set_guard_failed_state(report, control, error.errno);
Err(guard_required_error(
base,
total_len,
control,
error.errno,
*report,
))
}
}
}
fn guard_required_error(
base: NonNull<u8>,
total_len: usize,
control: ProtectionControl,
code: i32,
report: ProtectionReport,
) -> ProtectionError {
ProtectionError {
failure: ProtectionFailure { control, code },
partial_report: report,
rollback: rollback_guarded_mapping(base, total_len),
}
}
fn rollback_guarded_mapping(base: NonNull<u8>, total_len: usize) -> RollbackReport {
let unlock = RollbackState::NotNeeded;
let unmap = match unmap_guarded(base, total_len) {
Ok(()) => RollbackState::Completed,
Err(error) => RollbackState::Failed(ProtectionFailure {
control: ProtectionControl::Mapping,
code: error.errno,
}),
};
RollbackReport { unlock, unmap }
}
fn set_guard_failed_state(report: &mut ProtectionReport, control: ProtectionControl, code: i32) {
let state = ProtectionState::Failed { code };
match control {
ProtectionControl::Mapping => report.mapping = state,
ProtectionControl::MemoryLock => report.memory_lock = state,
ProtectionControl::DumpExclusion => report.dump_exclusion = state,
ProtectionControl::ForkPolicy => report.fork.state = state,
ProtectionControl::GuardPages => report.guard_pages = state,
ProtectionControl::Canary => report.canary = state,
ProtectionControl::CachePolicy => report.cache_policy = state,
}
}
fn protection_error_as_guard_page(error: ProtectionError) -> GuardPageError {
if let RollbackState::Failed(failure) = error.rollback.unmap {
return GuardPageError {
operation: GuardPageOperation::Unmap,
errno: failure.code,
};
}
if let RollbackState::Failed(failure) = error.rollback.unlock {
return GuardPageError {
operation: GuardPageOperation::Unlock,
errno: failure.code,
};
}
GuardPageError {
operation: match error.failure.control {
ProtectionControl::Mapping => GuardPageOperation::Map,
ProtectionControl::MemoryLock => GuardPageOperation::Lock,
ProtectionControl::DumpExclusion => GuardPageOperation::DontDump,
ProtectionControl::ForkPolicy => match error.partial_report.fork.policy {
ForkPolicy::WipeChild => GuardPageOperation::WipeOnFork,
ForkPolicy::Inherit | ForkPolicy::Exclude => GuardPageOperation::DontFork,
},
ProtectionControl::GuardPages => GuardPageOperation::Protect,
ProtectionControl::Canary => GuardPageOperation::Random,
ProtectionControl::CachePolicy => GuardPageOperation::Protect,
},
errno: error.failure.code,
}
}
#[inline]
const fn lock_quota_likely(code: i32) -> bool {
matches!(code, 11 | 12 | 1453)
}
#[inline]
const fn dump_exclusion_supported() -> bool {
cfg!(all(
feature = "memory-lock",
any(target_os = "linux", target_os = "freebsd")
))
}
#[inline]
const fn fork_exclusion_supported() -> bool {
cfg!(target_os = "linux")
}
#[inline]
const fn wipe_child_supported() -> bool {
cfg!(target_os = "linux")
}
#[cfg(feature = "memory-lock")]
fn guard_lock_mapping(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
lock_mapping(ptr, len)
}
#[cfg(not(feature = "memory-lock"))]
fn guard_lock_mapping(_ptr: NonNull<u8>, _len: usize) -> Result<(), GuardPageError> {
Err(GuardPageError {
operation: GuardPageOperation::Lock,
errno: 0,
})
}
#[cfg(feature = "memory-lock")]
fn guard_mark_dontdump(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
mark_dontdump(ptr, len)
}
#[cfg(not(feature = "memory-lock"))]
fn guard_mark_dontdump(_ptr: NonNull<u8>, _len: usize) -> Result<(), GuardPageError> {
Err(GuardPageError {
operation: GuardPageOperation::DontDump,
errno: 0,
})
}
fn guard_mark_dontfork(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
mark_dontfork(ptr, len)
}
fn guard_mark_wipeonfork(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
mark_wipeonfork(ptr, len)
}
#[cfg(feature = "random-canary")]
fn random_canary_value() -> Result<crate::canary::CanaryMaterial, GuardPageError> {
crate::canary::CanaryMaterial::random().map_err(|errno| GuardPageError {
operation: GuardPageOperation::Random,
errno,
})
}
fn rounded_data_len(len: usize) -> Result<usize, GuardPageError> {
let page_granule = platform_page_granule();
len.max(1)
.checked_add(page_granule - 1)
.map(|value| value & !(page_granule - 1))
.ok_or(GuardPageError {
operation: GuardPageOperation::Length,
errno: 0,
})
}
#[cfg(feature = "canary-check")]
fn guarded_payload_capacity(requested: usize) -> Result<usize, GuardPageError> {
let requested_with_canaries =
requested
.checked_add(guarded_extra_len())
.ok_or(GuardPageError {
operation: GuardPageOperation::Length,
errno: 0,
})?;
rounded_data_len(requested_with_canaries).map(|writable_len| {
writable_len
.saturating_sub(guarded_extra_len())
.max(requested)
})
}
#[cfg(not(feature = "canary-check"))]
fn guarded_payload_capacity(requested: usize) -> Result<usize, GuardPageError> {
rounded_data_len(requested)
}
#[cfg(feature = "canary-check")]
fn guarded_writable_len(payload_capacity: usize) -> Result<usize, GuardPageError> {
payload_capacity
.checked_add(guarded_extra_len())
.ok_or(GuardPageError {
operation: GuardPageOperation::Length,
errno: 0,
})
}
#[cfg(not(feature = "canary-check"))]
fn guarded_writable_len(payload_capacity: usize) -> Result<usize, GuardPageError> {
Ok(payload_capacity)
}
#[cfg(feature = "canary-check")]
#[inline]
const fn guarded_extra_len() -> usize {
CANARY_SIZE * 2
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
#[inline]
const fn platform_page_granule() -> usize {
LINUX_PAGE_GRANULE
}
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
#[inline]
fn platform_page_granule() -> usize {
crate::platform::linux_aarch64_page_size::detect_page_granule()
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
#[inline]
fn platform_page_granule() -> usize {
let page_size = unsafe { getpagesize() };
if page_size > 0 && (page_size as usize).is_power_of_two() {
page_size as usize
} else {
UNIX_FALLBACK_PAGE_GRANULE
}
}
#[cfg(target_os = "windows")]
#[inline]
fn platform_page_granule() -> usize {
let mut info = MaybeUninit::<SystemInfo>::zeroed();
unsafe {
GetSystemInfo(info.as_mut_ptr());
let page_size = info.assume_init().page_size as usize;
if page_size != 0 && page_size.is_power_of_two() {
page_size
} else {
WINDOWS_FALLBACK_PAGE_GRANULE
}
}
}
#[cfg(target_os = "linux")]
fn syscall_failed(ret: isize) -> bool {
(-4095..=-1).contains(&ret)
}
#[cfg(target_os = "linux")]
fn syscall_error(operation: GuardPageOperation, ret: isize) -> GuardPageError {
GuardPageError {
operation,
errno: (-ret) as i32,
}
}
#[cfg(target_os = "windows")]
fn windows_error(operation: GuardPageOperation) -> GuardPageError {
let errno = unsafe { GetLastError() } as i32;
GuardPageError { operation, errno }
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
fn unix_error(operation: GuardPageOperation) -> GuardPageError {
GuardPageError {
operation,
errno: unix_errno(),
}
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
fn unix_errno() -> i32 {
unsafe { *errno_location() as i32 }
}
#[cfg(target_os = "linux")]
fn map_guarded(len: usize) -> Result<NonNull<u8>, GuardPageError> {
let ret = raw_syscall6(
SYS_MMAP,
0,
len,
PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS,
MAP_FD_ANONYMOUS,
0,
);
if syscall_failed(ret) {
return Err(syscall_error(GuardPageOperation::Map, ret));
}
NonNull::new(ret as *mut u8).ok_or(GuardPageError {
operation: GuardPageOperation::Map,
errno: 0,
})
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
fn map_guarded(len: usize) -> Result<NonNull<u8>, GuardPageError> {
let ptr = unsafe {
mmap(
core::ptr::null_mut(),
len,
PROT_NONE as i32,
(MAP_PRIVATE | MAP_ANONYMOUS) as i32,
-1,
0,
)
};
if ptr as isize == -1 {
return Err(unix_error(GuardPageOperation::Map));
}
NonNull::new(ptr.cast::<u8>()).ok_or(GuardPageError {
operation: GuardPageOperation::Map,
errno: 0,
})
}
#[cfg(target_os = "windows")]
fn map_guarded(len: usize) -> Result<NonNull<u8>, GuardPageError> {
let ptr = unsafe {
VirtualAlloc(
core::ptr::null_mut(),
len,
MEM_COMMIT | MEM_RESERVE,
PAGE_NOACCESS,
)
};
NonNull::new(ptr.cast::<u8>()).ok_or_else(|| windows_error(GuardPageOperation::Map))
}
#[cfg(target_os = "linux")]
fn protect_data(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let ret = raw_syscall3(
SYS_MPROTECT,
ptr.as_ptr() as usize,
len,
PROT_READ | PROT_WRITE,
);
if syscall_failed(ret) {
Err(syscall_error(GuardPageOperation::Protect, ret))
} else {
Ok(())
}
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
fn protect_data(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let ret = unsafe {
mprotect(
ptr.as_ptr().cast::<c_void>(),
len,
(PROT_READ | PROT_WRITE) as i32,
)
};
if ret != 0 {
Err(unix_error(GuardPageOperation::Protect))
} else {
Ok(())
}
}
#[cfg(target_os = "windows")]
fn protect_data(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let mut old_protect = 0_u32;
let ret = unsafe {
VirtualProtect(
ptr.as_ptr().cast::<c_void>(),
len,
PAGE_READWRITE,
&mut old_protect,
)
};
if ret == 0 {
Err(windows_error(GuardPageOperation::Protect))
} else {
Ok(())
}
}
#[cfg(all(feature = "page-seal", target_os = "linux"))]
fn seal_data(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let ret = raw_syscall3(SYS_MPROTECT, ptr.as_ptr() as usize, len, PROT_NONE);
if syscall_failed(ret) {
Err(syscall_error(GuardPageOperation::Protect, ret))
} else {
Ok(())
}
}
#[cfg(any(
all(feature = "page-seal", target_os = "macos"),
all(feature = "page-seal", target_os = "ios"),
all(feature = "page-seal", target_os = "android"),
all(feature = "page-seal", target_os = "freebsd"),
all(feature = "page-seal", target_os = "openbsd"),
all(feature = "page-seal", target_os = "netbsd"),
all(feature = "page-seal", target_os = "dragonfly"),
))]
fn seal_data(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let ret = unsafe { mprotect(ptr.as_ptr().cast::<c_void>(), len, PROT_NONE as i32) };
if ret != 0 {
Err(unix_error(GuardPageOperation::Protect))
} else {
Ok(())
}
}
#[cfg(all(feature = "page-seal", target_os = "windows"))]
fn seal_data(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let mut old_protect = 0_u32;
let ret = unsafe {
VirtualProtect(
ptr.as_ptr().cast::<c_void>(),
len,
PAGE_NOACCESS,
&mut old_protect,
)
};
if ret == 0 {
Err(windows_error(GuardPageOperation::Protect))
} else {
Ok(())
}
}
#[cfg(feature = "page-seal")]
#[derive(Clone, Copy)]
enum PageProtection {
ReadWrite,
#[cfg(test)]
NoAccess,
}
#[cfg(feature = "page-seal")]
fn apply_page_protection(
ptr: NonNull<u8>,
len: usize,
protection: PageProtection,
) -> Result<(), GuardPageError> {
match protection {
PageProtection::ReadWrite => protect_data(ptr, len),
#[cfg(test)]
PageProtection::NoAccess => seal_data(ptr, len),
}
}
#[cfg(feature = "page-seal")]
fn normalize_data_pages(
ptr: NonNull<u8>,
len: usize,
protection: PageProtection,
) -> Result<(), GuardPageError> {
let page_granule = platform_page_granule();
let mut first_error = None;
for offset in (0..len).step_by(page_granule) {
let page = unsafe { NonNull::new_unchecked(ptr.as_ptr().add(offset)) };
if let Err(error) = apply_page_protection(page, page_granule, protection) {
if first_error.is_none() {
first_error = Some(error);
}
}
}
match first_error {
Some(error) => Err(error),
None => Ok(()),
}
}
#[cfg(feature = "page-seal")]
fn erase_and_reseal_data_pages(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let mut observed_reseal_failure = None;
erase_and_reseal_data_pages_inner(ptr, len, None, None, &mut observed_reseal_failure)
}
#[cfg(all(feature = "page-seal", test))]
fn erase_and_reseal_data_pages_with_failures(
ptr: NonNull<u8>,
len: usize,
failed_write_page: Option<usize>,
failed_reseal_page: Option<usize>,
) -> (Result<(), GuardPageError>, Option<usize>) {
let mut observed_reseal_failure = None;
let result = erase_and_reseal_data_pages_inner(
ptr,
len,
failed_write_page,
failed_reseal_page,
&mut observed_reseal_failure,
);
(result, observed_reseal_failure)
}
#[cfg(feature = "page-seal")]
fn erase_and_reseal_data_pages_inner(
ptr: NonNull<u8>,
len: usize,
_failed_write_page: Option<usize>,
_failed_reseal_page: Option<usize>,
_observed_reseal_failure: &mut Option<usize>,
) -> Result<(), GuardPageError> {
let page_granule = platform_page_granule();
let mut first_error = None;
for offset in (0..len).step_by(page_granule) {
let page = unsafe { NonNull::new_unchecked(ptr.as_ptr().add(offset)) };
#[cfg(test)]
if _failed_write_page == Some(offset / page_granule) {
let _ = seal_data(page, page_granule);
if first_error.is_none() {
first_error = Some(GuardPageError {
operation: GuardPageOperation::Protect,
errno: 0,
});
}
continue;
}
if let Err(error) = protect_data(page, page_granule) {
if first_error.is_none() {
first_error = Some(error);
}
continue;
}
crate::wipe_backend::erase(page.as_ptr(), page_granule);
#[cfg(test)]
if _failed_reseal_page == Some(offset / page_granule) {
*_observed_reseal_failure = Some(offset / page_granule);
if first_error.is_none() {
first_error = Some(GuardPageError {
operation: GuardPageOperation::Protect,
errno: 0,
});
}
continue;
}
if let Err(error) = seal_data(page, page_granule) {
if first_error.is_none() {
first_error = Some(error);
}
}
}
match first_error {
Some(error) => Err(error),
None => Ok(()),
}
}
#[cfg(all(feature = "page-seal", test))]
fn simulate_partial_transition(
ptr: NonNull<u8>,
len: usize,
protection: PageProtection,
) -> Result<(), GuardPageError> {
if len != 0 {
apply_page_protection(ptr, platform_page_granule(), protection)?;
}
Ok(())
}
#[cfg(all(feature = "memory-lock", target_os = "linux"))]
fn lock_mapping(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let ret = raw_syscall2(SYS_MLOCK, ptr.as_ptr() as usize, len);
if syscall_failed(ret) {
Err(syscall_error(GuardPageOperation::Lock, ret))
} else {
Ok(())
}
}
#[cfg(all(
feature = "memory-lock",
any(
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
)
))]
fn lock_mapping(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let ret = unsafe { mlock(ptr.as_ptr().cast::<c_void>(), len) };
if ret != 0 {
Err(unix_error(GuardPageOperation::Lock))
} else {
Ok(())
}
}
#[cfg(all(feature = "memory-lock", target_os = "windows"))]
fn lock_mapping(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let ret = unsafe { VirtualLock(ptr.as_ptr().cast::<c_void>(), len) };
if ret == 0 {
Err(windows_error(GuardPageOperation::Lock))
} else {
Ok(())
}
}
#[cfg(all(feature = "memory-lock", target_os = "linux"))]
fn mark_dontdump(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let ret = raw_syscall3(SYS_MADVISE, ptr.as_ptr() as usize, len, MADV_DONTDUMP);
if syscall_failed(ret) {
Err(syscall_error(GuardPageOperation::DontDump, ret))
} else {
Ok(())
}
}
#[cfg(all(
feature = "memory-lock",
not(target_os = "linux"),
not(target_os = "freebsd")
))]
#[inline]
fn mark_dontdump(_ptr: NonNull<u8>, _len: usize) -> Result<(), GuardPageError> {
Err(GuardPageError {
operation: GuardPageOperation::DontDump,
errno: 0,
})
}
#[cfg(all(feature = "memory-lock", target_os = "freebsd"))]
fn mark_dontdump(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let ret = unsafe { madvise(ptr.as_ptr().cast::<c_void>(), len, MADV_NOCORE) };
if ret != 0 {
Err(unix_error(GuardPageOperation::DontDump))
} else {
Ok(())
}
}
#[cfg(target_os = "linux")]
fn mark_dontfork(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let ret = raw_syscall3(SYS_MADVISE, ptr.as_ptr() as usize, len, MADV_DONTFORK);
if syscall_failed(ret) {
Err(syscall_error(GuardPageOperation::DontFork, ret))
} else {
Ok(())
}
}
#[cfg(target_os = "linux")]
fn mark_wipeonfork(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let ret = raw_syscall3(SYS_MADVISE, ptr.as_ptr() as usize, len, MADV_WIPEONFORK);
if syscall_failed(ret) {
Err(syscall_error(GuardPageOperation::WipeOnFork, ret))
} else {
Ok(())
}
}
#[cfg(not(target_os = "linux"))]
#[inline]
fn mark_wipeonfork(_ptr: NonNull<u8>, _len: usize) -> Result<(), GuardPageError> {
Err(GuardPageError {
operation: GuardPageOperation::WipeOnFork,
errno: 0,
})
}
#[cfg(not(target_os = "linux"))]
#[inline]
fn mark_dontfork(_ptr: NonNull<u8>, _len: usize) -> Result<(), GuardPageError> {
Err(GuardPageError {
operation: GuardPageOperation::DontFork,
errno: 0,
})
}
#[cfg(all(feature = "memory-lock", target_os = "linux"))]
fn unlock_mapping(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let ret = raw_syscall2(SYS_MUNLOCK, ptr.as_ptr() as usize, len);
if syscall_failed(ret) {
Err(syscall_error(GuardPageOperation::Unlock, ret))
} else {
Ok(())
}
}
#[cfg(all(
feature = "memory-lock",
any(
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
)
))]
fn unlock_mapping(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let ret = unsafe { munlock(ptr.as_ptr().cast::<c_void>(), len) };
if ret != 0 {
Err(unix_error(GuardPageOperation::Unlock))
} else {
Ok(())
}
}
#[cfg(all(feature = "memory-lock", target_os = "windows"))]
fn unlock_mapping(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let ret = unsafe { VirtualUnlock(ptr.as_ptr().cast::<c_void>(), len) };
if ret == 0 {
Err(windows_error(GuardPageOperation::Unlock))
} else {
Ok(())
}
}
#[cfg(target_os = "linux")]
fn unmap_guarded(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let ret = raw_syscall2(SYS_MUNMAP, ptr.as_ptr() as usize, len);
if syscall_failed(ret) {
Err(syscall_error(GuardPageOperation::Unmap, ret))
} else {
Ok(())
}
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
fn unmap_guarded(ptr: NonNull<u8>, len: usize) -> Result<(), GuardPageError> {
let ret = unsafe { munmap(ptr.as_ptr().cast::<c_void>(), len) };
if ret != 0 {
Err(unix_error(GuardPageOperation::Unmap))
} else {
Ok(())
}
}
#[cfg(target_os = "windows")]
fn unmap_guarded(ptr: NonNull<u8>, _len: usize) -> Result<(), GuardPageError> {
let ret = unsafe { VirtualFree(ptr.as_ptr().cast::<c_void>(), 0, MEM_RELEASE) };
if ret == 0 {
Err(windows_error(GuardPageOperation::Unmap))
} else {
Ok(())
}
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn raw_syscall2(number: usize, arg1: usize, arg2: usize) -> isize {
raw_syscall6(number, arg1, arg2, 0, 0, 0, 0)
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn raw_syscall3(number: usize, arg1: usize, arg2: usize, arg3: usize) -> isize {
raw_syscall6(number, arg1, arg2, arg3, 0, 0, 0)
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn raw_syscall6(
number: usize,
arg1: usize,
arg2: usize,
arg3: usize,
arg4: usize,
arg5: usize,
arg6: usize,
) -> isize {
let ret: isize;
unsafe {
asm!(
"syscall",
inlateout("rax") number as isize => ret,
in("rdi") arg1,
in("rsi") arg2,
in("rdx") arg3,
in("r10") arg4,
in("r8") arg5,
in("r9") arg6,
lateout("rcx") _,
lateout("r11") _,
options(nostack)
);
}
ret
}
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
fn raw_syscall2(number: usize, arg1: usize, arg2: usize) -> isize {
raw_syscall6(number, arg1, arg2, 0, 0, 0, 0)
}
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
fn raw_syscall3(number: usize, arg1: usize, arg2: usize, arg3: usize) -> isize {
raw_syscall6(number, arg1, arg2, arg3, 0, 0, 0)
}
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
fn raw_syscall6(
number: usize,
arg1: usize,
arg2: usize,
arg3: usize,
arg4: usize,
arg5: usize,
arg6: usize,
) -> isize {
let ret: isize;
unsafe {
asm!(
"svc 0",
inlateout("x0") arg1 as isize => ret,
in("x1") arg2,
in("x2") arg3,
in("x3") arg4,
in("x4") arg5,
in("x5") arg6,
in("x8") number,
options(nostack)
);
}
ret
}