Skip to main content

sanitization/
lib.rs

1#![no_std]
2#![deny(unsafe_code)]
3#![deny(unsafe_op_in_unsafe_fn)]
4
5//! Dependency-free secret memory sanitization for `no_std` Rust.
6//!
7//! The primary type is [`SecretBytes`], a fixed-size clear-on-drop container
8//! designed for secrets that are controlled from creation through destruction.
9//!
10//! Clearing routes through a small internal volatile-write backend. That backend
11//! uses one isolated unsafe boundary so the optimizer cannot remove secret
12//! clearing as a dead store.
13//!
14//! The [`ct`] module provides dependency-free data-oblivious primitives such as
15//! [`ct::Choice`], [`ct::ConstantTimeEq`], and explicit
16//! [`ct::Choice::declassify`] boundaries. Secret-controlled indexes and
17//! scalars use clear-on-drop [`ct::SecretIndex`] and [`ct::SecretScalar`]
18//! owners, while [`ct::SecretCtOption`] and [`ct::SecretCtResult`] manage
19//! secret-bearing dummy and unselected state. Its claim is no secret-dependent
20//! control flow or memory access under documented conditions, not identical
21//! wall-clock timing on every target.
22//!
23//! Important limits:
24//! - Safe Rust cannot soundly scrub old stack frames created by prior moves.
25//! - Process abort prevents destructors and post-closure cleanup from running.
26//!   Under `std`, [`sanitize_then_abort`] clears one explicitly supplied root
27//!   before a deliberate abort; it does not intercept arbitrary abort paths.
28//! - SIMD stores, broad memory policy, and target-specific hardening need
29//!   target-specific unsafe code and platform policy.
30//! - Platform memory locking is available only through the explicit
31//!   `memory-lock` feature on supported Linux, Android, macOS, iOS, Windows,
32//!   and BSD targets. On WASM, `memory-lock` must be paired with `wasm-compat`
33//!   to expose volatile-only compatibility types without host memory locking.
34//!   The same feature also enables pooled slots with [`SecretPool`] on
35//!   supported targets. The crate's own Miri unit tests use a test-only
36//!   aligned-allocation model for lifecycle and clear-before-release checks;
37//!   modeled report states do not prove that an OS protection was applied.
38//!   Downstream Miri execution of native mapped constructors is unsupported.
39//! - Locked, pooled, and guarded canary integrity checks are available only
40//!   through the explicit `canary-check` feature on supported targets.
41//! - OS-CSPRNG canary generation is available only through the explicit
42//!   `random-canary` feature.
43//! - The dependency-free default enables `asm-compare`, selecting the reviewed
44//!   x86_64/AArch64 assembly equality backend where available. Builds using
45//!   `default-features = false` retain the weaker portable fallback unless they
46//!   explicitly enable `asm-compare`.
47//! - Fail-closed assembly-backed equal-length byte comparison is available
48//!   through `strict-compare`. This feature does not strengthen ordering,
49//!   selection, lookup, or caller code. Other fail-closed profiles include
50//!   `strict-canary-check` and `require-fork-exclusion`.
51//! - Named native profiles bundle reviewed capabilities without claiming that
52//!   runtime protections succeeded. Use [`ProtectionRequest`] to inspect the
53//!   policy and [`ProtectionReport`] to inspect achieved controls.
54//! - Checked x86_64 cache-line eviction is available through the explicit
55//!   `cache-flush` feature. Other architectures and Miri return a structured
56//!   unsupported result after sanitizing helpers have still cleared memory.
57//! - Proc-macro derives are available only through the explicit `derive`
58//!   feature. The default build remains dependency-free.
59//! - `zeroize`, `subtle`, and `serde` integration are available only through
60//!   explicit `zeroize-interop`, `subtle-interop`, and `serde` features. They
61//!   are off by default.
62//! - UTF-8 validation, serde size-limit rejection, and public-length mismatch
63//!   handling are not data-oblivious operations. Callers must treat validity
64//!   and length as public metadata when using text or variable-length APIs.
65//! - Fixed-size lifetime enforcement is available only through the `std`
66//!   feature and [`ExpiringSecretBytes`].
67//! - Guard-page allocation is available only through the explicit
68//!   `guard-pages` feature on supported Linux, Android, macOS, iOS, Windows,
69//!   and BSD targets.
70//! - WASM has no kernel page table, `mlock`, `mprotect`, or native volatile
71//!   semantics. Base secret containers compile on WASM. `memory-lock` exposes
72//!   volatile-only compatibility types on WASM only when `wasm-compat` is also
73//!   enabled, so callers explicitly acknowledge the reduced guarantees.
74//!   `guard-pages` is rejected at compile time on WASM.
75//!
76//! # Migrating From 1.x
77//!
78//! Version 2 makes storage stability, CT declassification, derive safety,
79//! mapped integrity, and runtime protection outcomes explicit. The complete
80//! source-change inventory and replacement examples are maintained in
81//! [`docs/MIGRATION_2.0.md`](https://github.com/valkyoth/sanitization/blob/main/docs/MIGRATION_2.0.md).
82//! Generic `Secret<T>` exposure is governed by the conditional contracts in
83//! [`docs/STORAGE_CONTRACTS.md`](https://github.com/valkyoth/sanitization/blob/main/docs/STORAGE_CONTRACTS.md),
84//! and native hardening policy/report semantics are defined in
85//! [`docs/PROTECTION_REPORT.md`](https://github.com/valkyoth/sanitization/blob/main/docs/PROTECTION_REPORT.md).
86
87#[cfg(all(miri, test, not(debug_assertions)))]
88compile_error!(
89    "sanitization: the Miri protection simulator is restricted to debug test artifacts and must not be compiled into a release artifact"
90);
91
92#[cfg(all(
93    any(
94        feature = "profile-hardened-native",
95        feature = "profile-guarded-native",
96        feature = "profile-hardened-linux"
97    ),
98    target_arch = "wasm32"
99))]
100compile_error!(
101    "sanitization: native hardening profiles are unavailable on wasm32; use wasm-compat and inspect ProtectionReport for the explicit reduced-guarantee compatibility backend"
102);
103
104#[cfg(all(feature = "profile-hardened-linux", not(target_os = "linux")))]
105compile_error!(
106    "sanitization: profile-hardened-linux requires a Linux target because its fork-exclusion policy is Linux-specific"
107);
108
109#[cfg(all(
110    any(
111        feature = "profile-hardened-native",
112        feature = "profile-guarded-native"
113    ),
114    not(target_arch = "wasm32"),
115    not(any(
116        target_os = "linux",
117        target_os = "macos",
118        target_os = "ios",
119        target_os = "android",
120        target_os = "windows",
121        target_os = "freebsd",
122        target_os = "openbsd",
123        target_os = "netbsd",
124        target_os = "dragonfly"
125    ))
126))]
127compile_error!(
128    "sanitization: native hardening profiles require a reviewed native memory-lock backend"
129);
130
131#[cfg(all(
132    feature = "memory-lock",
133    target_arch = "wasm32",
134    not(feature = "wasm-compat")
135))]
136compile_error!(
137    "sanitization: memory-lock on wasm32 requires the wasm-compat feature; WASM has no mlock/mprotect, so this is an explicit reduced-guarantee compatibility backend"
138);
139
140#[cfg(all(feature = "guard-pages", target_arch = "wasm32"))]
141compile_error!(
142    "sanitization: the guard-pages feature is not supported on wasm32 because WASM linear memory has no page protection or mprotect equivalent"
143);
144
145#[cfg(all(
146    feature = "canary-check",
147    not(feature = "random-canary"),
148    target_arch = "wasm32"
149))]
150compile_error!(
151    "sanitization: canary-check on wasm32 requires random-canary because deterministic WASM canaries have no ASLR-backed entropy"
152);
153
154#[cfg(all(
155    feature = "strict-compare",
156    not(any(target_arch = "x86_64", target_arch = "aarch64")),
157    not(miri)
158))]
159compile_error!(
160    "sanitization: strict-compare requires an assembly comparison backend; currently supported on x86_64 and aarch64"
161);
162
163#[cfg(all(feature = "require-fork-exclusion", target_arch = "wasm32"))]
164compile_error!(
165    "sanitization: require-fork-exclusion is not supported on wasm32 because WASM has no fork inheritance policy"
166);
167
168#[cfg(feature = "alloc")]
169extern crate alloc;
170
171#[cfg(any(test, feature = "std"))]
172extern crate std;
173
174#[cfg(feature = "derive")]
175pub use sanitization_derive::{
176    ConditionallySelectable, ConstantTimeEq, SecureSanitize, SecureSanitizeOnDrop,
177};
178
179#[cfg(feature = "random-canary")]
180#[allow(unsafe_code)]
181#[cfg_attr(all(miri, test), allow(dead_code))]
182mod canary;
183
184mod platform;
185#[cfg(all(
186    feature = "asm-compare",
187    any(target_arch = "x86_64", target_arch = "aarch64"),
188    not(miri)
189))]
190pub(crate) use platform::compare_asm;
191#[allow(unused_imports)]
192pub use platform::*;
193
194#[allow(unsafe_code)]
195mod wipe_backend;
196
197/// Safe direct wiping helpers for ordinary buffers.
198pub mod wipe;
199
200/// Data-oblivious primitives for secret-handling code.
201///
202/// This module intentionally uses the familiar `ct` name, but its documented
203/// claim is narrower than "identical wall-clock time": APIs here are designed
204/// to avoid secret-dependent control flow and secret-dependent memory access
205/// under documented compiler, target, feature, and release-profile conditions.
206///
207/// Lengths, allocation behavior, panics, page faults, scheduling, and the final
208/// decision to branch on a secret-derived result are public effects. Use
209/// [`ct::Choice::declassify`] at that boundary so reviewers can search for it.
210pub mod ct;
211
212mod owned;
213pub use owned::*;
214#[allow(unused_imports)]
215pub(crate) use owned::{
216    constant_time_eq_equal_len, constant_time_eq_slices, portable_constant_time_eq_equal_len,
217};
218
219mod mapped;
220#[allow(unused_imports)]
221pub use mapped::*;
222
223mod interop;
224
225#[cfg(test)]
226mod tests;