|
109 | 109 | //!
|
110 | 110 | //! ## What guarantees does this crate provide?
|
111 | 111 | //!
|
112 |
| -//! Ideally a secure memory-zeroing function would guarantee the following: |
113 |
| -//! |
114 |
| -//! 1. Ensure the zeroing operation can't be "optimized away" by the compiler. |
115 |
| -//! 2. Ensure all subsequent reads to the memory following the zeroing operation |
116 |
| -//! will always see zeroes. |
117 |
| -//! |
118 |
| -//! This crate guarantees #1 is true: LLVM's volatile semantics ensure it. |
119 |
| -//! |
120 |
| -//! The story around #2 is much more complicated. In brief, it should be true |
121 |
| -//! that LLVM's current implementation does not attempt to perform |
122 |
| -//! optimizations which would allow a subsequent (non-volatile) read to see the |
123 |
| -//! original value prior to zeroization. However, this is not a guarantee, but |
124 |
| -//! rather an LLVM implementation detail, a.k.a. *undefined behavior*. |
125 |
| -//! It provides what we believe to be the best implementation possible on |
126 |
| -//! stable Rust, but we cannot yet make guarantees it will work reliably |
127 |
| -//! 100% of the time (particularly on exotic CPU architectures). |
128 |
| -//! |
129 |
| -//! For more background, we can look to the [core::ptr::write_volatile] |
130 |
| -//! documentation: |
131 |
| -//! |
132 |
| -//! > Volatile operations are intended to act on I/O memory, and are guaranteed |
133 |
| -//! > to not be elided or reordered by the compiler across other volatile |
134 |
| -//! > operations. |
135 |
| -//! > |
136 |
| -//! > Memory accessed with `read_volatile` or `write_volatile` should not be |
137 |
| -//! > accessed with non-volatile operations. |
138 |
| -//! |
139 |
| -//! Uhoh! This crate does not guarantee all reads to the memory it operates on |
140 |
| -//! are volatile, and the documentation for [core::ptr::write_volatile] |
141 |
| -//! explicitly warns against mixing volatile and non-volatile operations. |
142 |
| -//! Perhaps we'd be better off with something like a `VolatileCell` |
143 |
| -//! type which owns the associated data and ensures all reads and writes are |
144 |
| -//! volatile so we don't have to worry about the semantics of mixing volatile and |
145 |
| -//! non-volatile accesses. |
146 |
| -//! |
147 |
| -//! While that's a strategy worth pursuing (and something we may investigate |
148 |
| -//! separately from this crate), it comes with some onerous API requirements: |
149 |
| -//! it means any data that we might ever desire to zero is owned by a |
150 |
| -//! `VolatileCell`. However, this does not make it possible for this crate |
151 |
| -//! to act on references, which severely limits its applicability. In fact |
152 |
| -//! a `VolatileCell` can only act on values, i.e. to read a value from it, |
153 |
| -//! we'd need to make a copy of it, and that's literally the opposite of |
154 |
| -//! what we want. |
155 |
| -//! |
156 |
| -//! It's worth asking what the precise semantics of mixing volatile and |
157 |
| -//! non-volatile reads actually are, and whether a less obtrusive API which |
158 |
| -//! can act entirely on mutable references is possible, safe, and provides the |
159 |
| -//! desired behavior. |
160 |
| -//! |
161 |
| -//! Unfortunately, that's a tricky question, because |
162 |
| -//! [Rust does not have a formally defined memory model][memory-model], |
163 |
| -//! and the behavior of mixing volatile and non-volatile memory accesses is |
164 |
| -//! therefore not rigorously specified and winds up being an LLVM |
165 |
| -//! implementation detail. The semantics were discussed extensively in this |
166 |
| -//! thread, specifically in the context of zeroing secrets from memory: |
167 |
| -//! |
168 |
| -//! <https://internals.rust-lang.org/t/volatile-and-sensitive-memory/3188/24> |
169 |
| -//! |
170 |
| -//! Some notable details from this thread: |
171 |
| -//! |
172 |
| -//! - Rust/LLVM's notion of "volatile" is centered around data *accesses*, not |
173 |
| -//! the data itself. Specifically it maps to flags in LLVM IR which control |
174 |
| -//! the behavior of the optimizer, and is therefore a bit different from the |
175 |
| -//! typical C notion of "volatile". |
176 |
| -//! - As mentioned earlier, LLVM does not presently contain optimizations which |
177 |
| -//! would reorder a non-volatile read to occur before a volatile write. |
178 |
| -//! However, there is nothing precluding such optimizations from being added. |
179 |
| -//! LLVM presently appears to exhibit the desired behavior for point |
180 |
| -//! #2 above, but there is nothing preventing future versions of Rust |
181 |
| -//! and/or LLVM from changing that. |
182 |
| -//! |
183 |
| -//! To help mitigate concerns about reordering potentially exposing values |
184 |
| -//! after they have been zeroed, this crate leverages the [core::sync::atomic] |
185 |
| -//! memory fence functions including [compiler_fence] and [fence] (which uses |
186 |
| -//! the CPU's native fence instructions). These fences are leveraged with the |
187 |
| -//! strictest ordering guarantees, [Ordering::SeqCst], which ensures no |
188 |
| -//! accesses are reordered. Without a formally defined memory model we can't |
189 |
| -//! guarantee these will be effective, but we hope they will cover most cases. |
190 |
| -//! |
191 |
| -//! Concretely the threat of leaking "zeroized" secrets (via reordering by |
192 |
| -//! LLVM and/or the CPU via out-of-order or speculative execution) would |
193 |
| -//! require a non-volatile access to be reordered ahead of the following: |
194 |
| -//! |
195 |
| -//! 1. before an [Ordering::SeqCst] compiler fence |
196 |
| -//! 2. before an [Ordering::SeqCst] runtime fence |
197 |
| -//! 3. before a volatile write |
198 |
| -//! |
199 |
| -//! This seems unlikely, but our usage of mixed non-volatile and volatile |
200 |
| -//! accesses is technically undefined behavior, at least until guarantees |
201 |
| -//! about this particular mixture of operations is formally defined in a |
202 |
| -//! Rust memory model. |
203 |
| -//! |
204 |
| -//! Furthermore, given the recent history of microarchitectural attacks |
205 |
| -//! (Spectre, Meltdown, etc), there is also potential for "zeroized" secrets |
206 |
| -//! to be leaked through covert channels (e.g. memory fences have been used |
207 |
| -//! as a covert channel), so we are wary to make guarantees unless they can |
208 |
| -//! be made firmly in terms of both a formal Rust memory model and the |
209 |
| -//! generated code for a particular CPU architecture. |
210 |
| -//! |
211 |
| -//! In conclusion, this crate guarantees the zeroize operation will not be |
212 |
| -//! elided or "optimized away", makes a "best effort" to ensure that |
213 |
| -//! memory accesses will not be reordered ahead of the "zeroize" operation, |
214 |
| -//! but **cannot** yet guarantee that such reordering will not occur. |
215 |
| -//! |
216 |
| -//! In the future it might be possible to guarantee such behavior using |
217 |
| -//! [LLVM's "unordered" atomic mode][unordered], which is documented as |
218 |
| -//! being free of undefined behavior. There's an open issue to |
219 |
| -//! [expose atomic memcpy/memset in core/std][llvm-atomic] |
220 |
| -//! in which case this crate could leverage them to provide well-defined |
221 |
| -//! guarantees that zeroization will always occur. |
| 112 | +//! This crate guarantees the following: |
| 113 | +//! |
| 114 | +//! 1. The zeroing operation can't be "optimized away" by the compiler. |
| 115 | +//! 2. All subsequent reads to memory will see "zeroized" values. |
| 116 | +//! |
| 117 | +//! LLVM's volatile semantics ensure #1 is true. |
| 118 | +//! |
| 119 | +//! Additionally, thanks to work by the [Unsafe Code Guidelines Working Group], |
| 120 | +//! we can now fairly confidently say #2 is true as well. Previously there were |
| 121 | +//! worries that the approach used by this crate (mixing volatile and |
| 122 | +//! non-volatile accesses) was undefined behavior due to language contained |
| 123 | +//! in the documentation for `write_volatile`, however after some discussion |
| 124 | +//! [these remarks have been removed] and the specific usage pattern in this |
| 125 | +//! crate is considered to be well-defined. |
| 126 | +//! |
| 127 | +//! To help mitigate concerns about reordering of operations executed by the |
| 128 | +//! CPU potentially exposing values after they have been zeroed, this crate |
| 129 | +//! leverages the [core::sync::atomic] memory fence functions including |
| 130 | +//! [compiler_fence] and [fence] (which uses the CPU's native fence |
| 131 | +//! instructions). These fences are leveraged with the strictest ordering |
| 132 | +//! guarantees, [Ordering::SeqCst], which ensures no accesses are reordered. |
| 133 | +//! |
| 134 | +//! All of that said, there is still potential for microarchitectural attacks |
| 135 | +//! (ala Spectre/Meltdown) to leak "zeroized" secrets through covert channels |
| 136 | +//! (e.g. the memory fences mentioned above have previously been used as a |
| 137 | +//! covert channel in the Foreshadow attack). This crate makes no guarantees |
| 138 | +//! that zeroized values cannot be leaked through such channels, as they |
| 139 | +//! represent flaws in the underlying hardware. |
222 | 140 | //!
|
223 | 141 | //! ## Stack/Heap Zeroing Notes
|
224 | 142 | //!
|
|
240 | 158 | //! attempting to zeroize such buffers to initialize them to the correct
|
241 | 159 | //! capacity, and take care to prevent subsequent reallocation.
|
242 | 160 | //!
|
243 |
| -//! This crate does not intend to implement higher-level abstractions to |
244 |
| -//! eliminate these risks, instead it merely makes a best effort to clear the |
245 |
| -//! memory it's aware of. |
| 161 | +//! The `secrecy` crate provides higher-level abstractions for eliminating |
| 162 | +//! usage patterns which can cause reallocations: |
246 | 163 | //!
|
247 |
| -//! Crates which are built on `zeroize` and provide higher-level abstractions |
248 |
| -//! for strategically avoiding these problems would certainly be interesting! |
249 |
| -//! (and something we may consider developing in the future) |
| 164 | +//! <https://crates.io/crates/secrecy> |
250 | 165 | //!
|
251 | 166 | //! ## What about: clearing registers, mlock, mprotect, etc?
|
252 | 167 | //!
|
253 |
| -//! This crate is laser-focused on being a simple, unobtrusive crate for zeroing |
254 |
| -//! memory in as reliable a manner as is possible on stable Rust. |
| 168 | +//! This crate is focused on providing simple, unobtrusive support for reliably |
| 169 | +//! zeroing memory using the best approach possible on stable Rust. |
255 | 170 | //!
|
256 | 171 | //! Clearing registers is a difficult problem that can't easily be solved by
|
257 | 172 | //! something like a crate, and requires either inline ASM or rustc support.
|
|
276 | 191 | //! [DefaultIsZeroes]: https://docs.rs/zeroize/latest/zeroize/trait.DefaultIsZeroes.html
|
277 | 192 | //! [Default]: https://doc.rust-lang.org/std/default/trait.Default.html
|
278 | 193 | //! [core::ptr::write_volatile]: https://doc.rust-lang.org/core/ptr/fn.write_volatile.html
|
| 194 | +//! [Unsafe Code Guidelines Working Group]: https://github.com/rust-lang/unsafe-code-guidelines |
| 195 | +//! [these remarks have been removed]: https://github.com/rust-lang/rust/pull/60972 |
279 | 196 | //! [core::sync::atomic]: https://doc.rust-lang.org/stable/core/sync/atomic/index.html
|
280 | 197 | //! [Ordering::SeqCst]: https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html#variant.SeqCst
|
281 | 198 | //! [compiler_fence]: https://doc.rust-lang.org/stable/core/sync/atomic/fn.compiler_fence.html
|
|
0 commit comments