Description
Proposal
Problem statement
This adds a fill_explicit
function, which allows a slice of T
to be cleared, which the guarantee or strong recommendation that implementations do not elide any store. This is useful in secure code where cleaning secrets from memory is necessary, and failing to do so by accident or by malicious compiler optimization.
Motivation, use-cases
The primary motivation is secure/cryptographic code. Specific crates that could benefit would be the zeroize crate (as it could replace use of write_volatile
), and any rust implementation of cryptographic algorithms, especially ciphers.
Solution sketches
Two apis are proposed, one for mutable references to slices (mirroring the [T]::set
function) and one for pointers (mirroring the core::ptr::write_bytes
function). Both are suggested together, though if not both is accepted, the strong recommendation is the pointer one. The functions are not the same as write_volatile
or read_volatile
(in particular, it should be permissible to reorder them relative to unrelated side effects) and are not suited for use on MMIO.
The proposed APIs are
// in core::slice
impl<T> [T]{
/// Fills the slice with copies of `T`.
/// The implementation shall ensure that calls to this function are not removed
pub fn fill_explicit(&mut self, value: T) where T: Copy;
}
// in core::ptr
/// Writes `len*size_of::<T>()` bytes to p
/// The implementation shall ensure that calls to this function are not removed
pub unsafe fn write_bytes_explicit<T>(p: *mut T, b: u8, len: usize);
impl<T> *mut T{
/// Writes `len*size_of::<T>()` bytes to p
/// The implementation shall ensure that calls to this function are not removed
pub unsafe fn write_bytes_explicit(self, b: u8, count: usize);
}
Implementation wise, the functions are quite flexible. A naive, pure rust, implementation could simply use a loop to write the bytes/values. write_bytes_explicit
could also be vectorized in pure rust (either using existing SIMD intrinsics, or merely a large type). A more efficient implementation could call a compiler-specific intrinsic, but this proposal does not specify such an intrinsic. The most basic implementation of fill_explict
is:
pub fn fill_explicit(&mut self, val: T) where T: Copy{
for v in self{
unsafe{(v as *mut T).write_volatile(val)}
}
}
As an alternative solution, fill_explicit
could have a signature that takes a u8
(if also declared unsafe), which acts as a slice version of write_bytes_explicit
. This would be relatively less useful than the current signature, as slice.fill_bytes_explict(0u8)
could be written as let len = slice.len(); core::ptr::write_bytes_explicit(slice.as_mut_ptr(),0u8,len)
.
The APIs above require an implementation not remove them. As a relaxation, it could be similar in nature to core::hint::black_box
, where removing the function is a QoI issue, rather than a compliance issue. I would expect that there would be limited to no distinction, as, unlike black_box
, it seems difficult to rely on for soundness and any implementation that removed the call would most likely have a bug filed within weeks or days if not hours or minutes. No further requirements are placed on the functions, but it should be recommended that no copies of the data that are made for w/e reason persist beyond the return of the function.
Links and related work
C paper (accepted in C23): N2897.
What happens now?
This issue is part of the libs-api team API change proposal process. Once this issue is filed the libs-api team will review open proposals in its weekly meeting. You should receive feedback within a week or two.
An ACP was chosen rather than an RFC because a pure rust implementation exists (and use of intrinsics)