use core::fmt;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CanaryCorruptedError;
impl fmt::Display for CanaryCorruptedError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("mapped secret canary corrupted")
}
}
#[cfg(feature = "std")]
impl std::error::Error for CanaryCorruptedError {}
pub type IntegrityResult<T> = Result<T, CanaryCorruptedError>;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SecretIntegrityError<E> {
Canary(CanaryCorruptedError),
Operation(E),
}
pub type MappedResult<T, E> = Result<T, SecretIntegrityError<E>>;
pub type SecretIntegrityResult<T, E> = MappedResult<T, E>;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BoundedMappedSecretError<E> {
CapacityLimit {
maximum: usize,
actual: usize,
},
CapacityOverflow {
maximum: usize,
},
Integrity(CanaryCorruptedError),
Operation(E),
}
impl<E> From<SecretIntegrityError<E>> for BoundedMappedSecretError<E> {
#[inline]
fn from(error: SecretIntegrityError<E>) -> Self {
match error {
SecretIntegrityError::Canary(error) => Self::Integrity(error),
SecretIntegrityError::Operation(error) => Self::Operation(error),
}
}
}
impl<E: fmt::Display> fmt::Display for BoundedMappedSecretError<E> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CapacityLimit { maximum, actual } => write!(
formatter,
"mapped secret length {actual} exceeds permanent maximum {maximum}"
),
Self::CapacityOverflow { maximum } => write!(
formatter,
"mapped secret length overflowed its permanent maximum {maximum}"
),
Self::Integrity(error) => error.fmt(formatter),
Self::Operation(error) => error.fmt(formatter),
}
}
}
#[cfg(feature = "std")]
impl<E> std::error::Error for BoundedMappedSecretError<E>
where
E: std::error::Error + 'static,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::CapacityLimit { .. } | Self::CapacityOverflow { .. } => None,
Self::Integrity(error) => Some(error),
Self::Operation(error) => Some(error),
}
}
}
impl<E> SecretIntegrityError<E> {
#[must_use]
pub const fn is_canary(&self) -> bool {
matches!(self, Self::Canary(_))
}
#[must_use]
pub const fn is_operation(&self) -> bool {
matches!(self, Self::Operation(_))
}
#[must_use]
pub const fn operation(&self) -> Option<&E> {
match self {
Self::Canary(_) => None,
Self::Operation(error) => Some(error),
}
}
pub fn map_operation<O>(self, map: impl FnOnce(E) -> O) -> SecretIntegrityError<O> {
match self {
Self::Canary(error) => SecretIntegrityError::Canary(error),
Self::Operation(error) => SecretIntegrityError::Operation(map(error)),
}
}
}
pub trait SecretIntegrityResultExt<T, E> {
fn flatten_secret_integrity(self) -> MappedResult<T, E>;
}
impl<T, E> SecretIntegrityResultExt<T, E> for Result<Result<T, E>, CanaryCorruptedError> {
#[inline]
fn flatten_secret_integrity(self) -> MappedResult<T, E> {
match self {
Ok(Ok(value)) => Ok(value),
Ok(Err(error)) => Err(SecretIntegrityError::Operation(error)),
Err(error) => Err(SecretIntegrityError::Canary(error)),
}
}
}
impl<E: fmt::Display> fmt::Display for SecretIntegrityError<E> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Canary(error) => error.fmt(formatter),
Self::Operation(error) => error.fmt(formatter),
}
}
}
#[cfg(feature = "std")]
impl<E> std::error::Error for SecretIntegrityError<E>
where
E: std::error::Error + 'static,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Canary(error) => Some(error),
Self::Operation(error) => Some(error),
}
}
}
impl<E> From<CanaryCorruptedError> for SecretIntegrityError<E> {
#[inline]
fn from(error: CanaryCorruptedError) -> Self {
Self::Canary(error)
}
}
impl From<crate::LengthError> for SecretIntegrityError<crate::LengthError> {
#[inline]
fn from(error: crate::LengthError) -> Self {
Self::Operation(error)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct SecretPoolSlotId {
pub index: usize,
pub generation: usize,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SecretPoolReport {
pub slot_size: usize,
pub slot_stride: usize,
pub capacity_slots: usize,
pub live_slots: usize,
pub quarantined_slots: usize,
pub payload_capacity_bytes: usize,
pub reserved_bytes: usize,
pub mapped_bytes: usize,
pub locked_bytes: usize,
pub mapping_overhead_bytes: usize,
pub locked_overhead_bytes: usize,
pub page_granule: usize,
pub lock_quota_likely: bool,
}
impl SecretPoolReport {
#[must_use]
pub const fn storage_efficiency_basis_points(&self) -> Option<u16> {
efficiency_basis_points(self.payload_capacity_bytes, self.reserved_bytes)
}
#[must_use]
pub const fn mapping_efficiency_basis_points(&self) -> Option<u16> {
efficiency_basis_points(self.payload_capacity_bytes, self.mapped_bytes)
}
#[must_use]
pub const fn lock_efficiency_basis_points(&self) -> Option<u16> {
efficiency_basis_points(self.payload_capacity_bytes, self.locked_bytes)
}
}
const fn efficiency_basis_points(payload: usize, total: usize) -> Option<u16> {
if total == 0 {
return None;
}
let value = ((payload as u128) * 10_000) / (total as u128);
Some(if value > 10_000 { 10_000 } else { value as u16 })
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Requirement {
Required,
Preferred,
NotRequested,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ForkPolicy {
Inherit,
Exclude,
WipeChild,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ForkProtectionRequest {
pub policy: ForkPolicy,
pub requirement: Requirement,
}
impl ForkProtectionRequest {
#[must_use]
pub const fn inherit() -> Self {
Self {
policy: ForkPolicy::Inherit,
requirement: Requirement::NotRequested,
}
}
#[must_use]
pub const fn exclude(requirement: Requirement) -> Self {
Self {
policy: ForkPolicy::Exclude,
requirement,
}
}
#[must_use]
pub const fn wipe_child(requirement: Requirement) -> Self {
Self {
policy: ForkPolicy::WipeChild,
requirement,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ProtectionRequest {
pub memory_lock: Requirement,
pub dump_exclusion: Requirement,
pub fork: ForkProtectionRequest,
pub guard_pages: Requirement,
pub canary: Requirement,
pub cache_policy: Requirement,
}
impl ProtectionRequest {
#[must_use]
pub const fn locked() -> Self {
Self {
memory_lock: Requirement::Required,
dump_exclusion: Requirement::Preferred,
fork: ForkProtectionRequest::exclude(compiled_fork_requirement()),
guard_pages: Requirement::NotRequested,
canary: compiled_canary_requirement(),
cache_policy: Requirement::NotRequested,
}
}
#[must_use]
pub const fn guarded() -> Self {
Self {
memory_lock: Requirement::NotRequested,
dump_exclusion: Requirement::NotRequested,
fork: ForkProtectionRequest::inherit(),
guard_pages: Requirement::Required,
canary: compiled_canary_requirement(),
cache_policy: Requirement::NotRequested,
}
}
#[cfg(feature = "page-seal")]
#[must_use]
pub const fn page_sealed() -> Self {
Self {
memory_lock: Requirement::NotRequested,
dump_exclusion: Requirement::NotRequested,
fork: page_sealed_fork_request(),
guard_pages: Requirement::Required,
canary: compiled_canary_requirement(),
cache_policy: Requirement::NotRequested,
}
}
#[must_use]
pub const fn locked_guarded() -> Self {
Self {
memory_lock: Requirement::Required,
dump_exclusion: Requirement::Preferred,
fork: ForkProtectionRequest::exclude(compiled_fork_requirement()),
guard_pages: Requirement::Required,
canary: compiled_canary_requirement(),
cache_policy: Requirement::NotRequested,
}
}
#[cfg(feature = "profile-hardened-native")]
#[must_use]
pub const fn profile_hardened_native() -> Self {
Self {
memory_lock: Requirement::Required,
dump_exclusion: Requirement::Preferred,
fork: ForkProtectionRequest::exclude(Requirement::Preferred),
guard_pages: Requirement::NotRequested,
canary: Requirement::Required,
cache_policy: Requirement::NotRequested,
}
}
#[cfg(feature = "profile-guarded-native")]
#[must_use]
pub const fn profile_guarded_native() -> Self {
Self {
guard_pages: Requirement::Required,
..Self::profile_hardened_native()
}
}
#[cfg(feature = "profile-hardened-linux")]
#[must_use]
pub const fn profile_hardened_linux() -> Self {
Self {
fork: ForkProtectionRequest::exclude(Requirement::Required),
..Self::profile_hardened_native()
}
}
#[must_use]
pub const fn wasm_compatibility() -> Self {
Self {
memory_lock: Requirement::Preferred,
dump_exclusion: Requirement::Preferred,
fork: ForkProtectionRequest::exclude(Requirement::Preferred),
guard_pages: Requirement::NotRequested,
canary: compiled_canary_requirement(),
cache_policy: Requirement::NotRequested,
}
}
}
#[cfg(all(feature = "page-seal", target_os = "linux"))]
const fn page_sealed_fork_request() -> ForkProtectionRequest {
ForkProtectionRequest::wipe_child(Requirement::Required)
}
#[cfg(all(feature = "page-seal", target_os = "windows"))]
const fn page_sealed_fork_request() -> ForkProtectionRequest {
ForkProtectionRequest::inherit()
}
#[cfg(all(
feature = "page-seal",
not(any(target_os = "linux", target_os = "windows"))
))]
const fn page_sealed_fork_request() -> ForkProtectionRequest {
ForkProtectionRequest::wipe_child(Requirement::Required)
}
#[cfg(feature = "canary-check")]
const fn compiled_canary_requirement() -> Requirement {
Requirement::Required
}
#[cfg(not(feature = "canary-check"))]
const fn compiled_canary_requirement() -> Requirement {
Requirement::NotRequested
}
#[cfg(feature = "require-fork-exclusion")]
const fn compiled_fork_requirement() -> Requirement {
Requirement::Required
}
#[cfg(not(feature = "require-fork-exclusion"))]
const fn compiled_fork_requirement() -> Requirement {
Requirement::Preferred
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProtectionState {
Established,
NotRequested,
NotApplicable,
Unsupported,
Failed {
code: i32,
},
CompatibilityOnly,
}
impl ProtectionState {
#[must_use]
pub const fn satisfies(self, requirement: Requirement) -> bool {
match requirement {
Requirement::NotRequested => true,
Requirement::Required | Requirement::Preferred => matches!(self, Self::Established),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ForkProtectionReport {
pub policy: ForkPolicy,
pub state: ProtectionState,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ProtectionReport {
pub mapping: ProtectionState,
pub memory_lock: ProtectionState,
pub dump_exclusion: ProtectionState,
pub fork: ForkProtectionReport,
pub guard_pages: ProtectionState,
pub canary: ProtectionState,
pub cache_policy: ProtectionState,
pub requested_bytes: usize,
pub mapped_bytes: usize,
pub locked_bytes: usize,
pub page_granule: usize,
pub lock_quota_likely: bool,
}
impl ProtectionReport {
#[must_use]
pub const fn satisfies(&self, request: ProtectionRequest) -> bool {
let empty = self.requested_bytes == 0;
mapping_satisfies(self.mapping, empty)
&& state_satisfies_for_storage(self.memory_lock, request.memory_lock, empty)
&& state_satisfies_for_storage(self.dump_exclusion, request.dump_exclusion, empty)
&& fork_policies_match(self.fork.policy, request.fork.policy)
&& state_satisfies_for_storage(self.fork.state, request.fork.requirement, empty)
&& state_satisfies_for_storage(self.guard_pages, request.guard_pages, empty)
&& state_satisfies_for_storage(self.canary, request.canary, empty)
&& state_satisfies_for_storage(self.cache_policy, request.cache_policy, empty)
}
#[must_use]
pub const fn all_requested_controls_established(&self, request: ProtectionRequest) -> bool {
self.satisfies(request)
}
#[must_use]
pub const fn is_degraded(&self) -> bool {
let empty = self.requested_bytes == 0;
!mapping_satisfies(self.mapping, empty)
|| protection_state_is_degraded(self.memory_lock, empty)
|| protection_state_is_degraded(self.dump_exclusion, empty)
|| protection_state_is_degraded(self.fork.state, empty)
|| protection_state_is_degraded(self.guard_pages, empty)
|| protection_state_is_degraded(self.canary, empty)
|| protection_state_is_degraded(self.cache_policy, empty)
}
#[must_use]
pub const fn memory_is_locked(&self) -> bool {
matches!(self.memory_lock, ProtectionState::Established)
}
#[must_use]
pub const fn guard_pages_established(&self) -> bool {
matches!(self.guard_pages, ProtectionState::Established)
}
pub fn failed_or_unsupported_controls(&self) -> impl Iterator<Item = ProtectionControl> {
let empty = self.requested_bytes == 0;
[
unavailable_control(ProtectionControl::Mapping, self.mapping, empty),
unavailable_control(ProtectionControl::MemoryLock, self.memory_lock, empty),
unavailable_control(ProtectionControl::DumpExclusion, self.dump_exclusion, empty),
unavailable_control(ProtectionControl::ForkPolicy, self.fork.state, empty),
unavailable_control(ProtectionControl::GuardPages, self.guard_pages, empty),
unavailable_control(ProtectionControl::Canary, self.canary, empty),
unavailable_control(ProtectionControl::CachePolicy, self.cache_policy, empty),
]
.into_iter()
.flatten()
}
#[allow(dead_code)]
pub(crate) const fn pending(
request: ProtectionRequest,
requested_bytes: usize,
page_granule: usize,
) -> Self {
Self {
mapping: ProtectionState::NotRequested,
memory_lock: initial_state(request.memory_lock),
dump_exclusion: initial_state(request.dump_exclusion),
fork: ForkProtectionReport {
policy: request.fork.policy,
state: initial_fork_state(request.fork),
},
guard_pages: initial_state(request.guard_pages),
canary: initial_state(request.canary),
cache_policy: initial_state(request.cache_policy),
requested_bytes,
mapped_bytes: 0,
locked_bytes: 0,
page_granule,
lock_quota_likely: false,
}
}
}
const fn mapping_satisfies(state: ProtectionState, empty: bool) -> bool {
matches!(state, ProtectionState::Established)
|| (empty && matches!(state, ProtectionState::NotApplicable))
}
const fn state_satisfies_for_storage(
state: ProtectionState,
requirement: Requirement,
empty: bool,
) -> bool {
match requirement {
Requirement::NotRequested => true,
Requirement::Required | Requirement::Preferred => {
matches!(state, ProtectionState::Established)
|| (empty && matches!(state, ProtectionState::NotApplicable))
}
}
}
const fn protection_state_is_degraded(state: ProtectionState, empty: bool) -> bool {
matches!(
state,
ProtectionState::Unsupported
| ProtectionState::Failed { .. }
| ProtectionState::CompatibilityOnly
) || (!empty && matches!(state, ProtectionState::NotApplicable))
}
const fn fork_policies_match(left: ForkPolicy, right: ForkPolicy) -> bool {
matches!(
(left, right),
(ForkPolicy::Inherit, ForkPolicy::Inherit)
| (ForkPolicy::Exclude, ForkPolicy::Exclude)
| (ForkPolicy::WipeChild, ForkPolicy::WipeChild)
)
}
fn unavailable_control(
control: ProtectionControl,
state: ProtectionState,
empty: bool,
) -> Option<ProtectionControl> {
if protection_state_is_degraded(state, empty) {
Some(control)
} else {
None
}
}
#[allow(dead_code)]
const fn initial_state(requirement: Requirement) -> ProtectionState {
match requirement {
Requirement::NotRequested => ProtectionState::NotRequested,
Requirement::Required | Requirement::Preferred => ProtectionState::Unsupported,
}
}
#[allow(dead_code)]
const fn initial_fork_state(request: ForkProtectionRequest) -> ProtectionState {
match request.policy {
ForkPolicy::Inherit => ProtectionState::Established,
ForkPolicy::Exclude | ForkPolicy::WipeChild => initial_state(request.requirement),
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProtectionControl {
Mapping,
MemoryLock,
DumpExclusion,
ForkPolicy,
GuardPages,
Canary,
CachePolicy,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ProtectionFailure {
pub control: ProtectionControl,
pub code: i32,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RollbackState {
NotNeeded,
Completed,
Failed(ProtectionFailure),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RollbackReport {
pub unlock: RollbackState,
pub unmap: RollbackState,
}
impl RollbackReport {
#[allow(dead_code)]
pub(crate) const fn not_needed() -> Self {
Self {
unlock: RollbackState::NotNeeded,
unmap: RollbackState::NotNeeded,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ProtectionError {
pub failure: ProtectionFailure,
pub partial_report: ProtectionReport,
pub rollback: RollbackReport,
}
impl fmt::Display for ProtectionError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"required protection {:?} failed with code {}; rollback: {:?}",
self.failure.control, self.failure.code, self.rollback
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ProtectionError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProtectedSecretFillError<E> {
CapacityLimit {
maximum: usize,
actual: usize,
},
Protection(ProtectionError),
Fill(E),
Integrity(CanaryCorruptedError),
Length(crate::LengthError),
}
impl<E: fmt::Display> fmt::Display for ProtectedSecretFillError<E> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CapacityLimit { maximum, actual } => write!(
formatter,
"protected secret capacity {actual} exceeds application maximum {maximum}"
),
Self::Protection(error) => error.fmt(formatter),
Self::Fill(error) => write!(formatter, "protected secret fill failed: {error}"),
Self::Integrity(error) => error.fmt(formatter),
Self::Length(error) => error.fmt(formatter),
}
}
}
#[cfg(feature = "std")]
impl<E> std::error::Error for ProtectedSecretFillError<E>
where
E: std::error::Error + 'static,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::CapacityLimit { .. } => None,
Self::Protection(error) => Some(error),
Self::Fill(error) => Some(error),
Self::Integrity(error) => Some(error),
Self::Length(error) => Some(error),
}
}
}
impl<E> From<ProtectionError> for ProtectedSecretFillError<E> {
#[inline]
fn from(error: ProtectionError) -> Self {
Self::Protection(error)
}
}
impl<E> From<crate::LengthError> for ProtectedSecretFillError<E> {
#[inline]
fn from(error: crate::LengthError) -> Self {
Self::Length(error)
}
}
#[allow(dead_code)]
pub(crate) const fn unavailable_state(requirement: Requirement) -> Result<ProtectionState, ()> {
match requirement {
Requirement::Required => Err(()),
Requirement::Preferred => Ok(ProtectionState::Unsupported),
Requirement::NotRequested => Ok(ProtectionState::NotRequested),
}
}
#[cfg(kani)]
pub(crate) const fn failed_state(
requirement: Requirement,
code: i32,
) -> Result<ProtectionState, ()> {
match requirement {
Requirement::Required => Err(()),
Requirement::Preferred => Ok(ProtectionState::Failed { code }),
Requirement::NotRequested => Ok(ProtectionState::NotRequested),
}
}
#[cfg(kani)]
mod verification {
use super::*;
#[kani::proof]
fn required_unavailable_never_degrades_to_success() {
assert!(unavailable_state(Requirement::Required).is_err());
}
#[kani::proof]
fn preferred_failure_is_reported_as_failed() {
let code: i32 = kani::any();
assert_eq!(
failed_state(Requirement::Preferred, code),
Ok(ProtectionState::Failed { code })
);
}
#[kani::proof]
fn not_requested_is_never_reported_established() {
assert_eq!(
unavailable_state(Requirement::NotRequested),
Ok(ProtectionState::NotRequested)
);
assert_eq!(
failed_state(Requirement::NotRequested, 7),
Ok(ProtectionState::NotRequested)
);
}
#[kani::proof]
fn inherit_policy_is_explicitly_established() {
assert_eq!(
initial_fork_state(ForkProtectionRequest::inherit()),
ProtectionState::Established
);
}
}