|
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