Uh oh!
There was an error while loading. Please reload this page.
zeroize: replace atomic_fence with optimization_barrier - #1252
Conversation
tarcieri
commented
Dec 15, 2025
I really don't understand the motivation for this or why you're making it a public function.Something like this seems like a potential replacement for the existing Can you start with a demonstration of a problem in the existing implementation, then show what this solves? Making it public seems like an implementation detail leaking out of the API. |
You can see a motivation example in the function docs. We can not "zeroize" types like I encountered a need for this type when working on block buffer for
Yes. I plan to do it in a separate PR. |
tarcieri
commented
Dec 15, 2025
I'm not sure I understand the issue in RustCrypto/stream-ciphers#491 or what it's even trying to do... zeroize the entire keystream it generates? |
The current With the // in rand_coreimpl<R:Generator>BlockRng<R>{pubfnzeroize(&mutself){self.results = Default::default();}}// in chacha20structChachaRng(BlockRng<ChaChaBlockRng>);implDropforChachaRng{fndrop(&mutself){self.0.zeroize();
zeroize::observe(self);}} |
tarcieri
commented
Dec 15, 2025
I'm still not sure I follow... what calls the Where exactly is the "hack"? |
tarcieri
commented
Dec 15, 2025
How is... implDropforChachaRng{fndrop(&mutself){self.0.zeroize();
zeroize::observe(self);}}...any different from... implDropforChachaRng{fndrop(&mutself){self.0.zeroize();}} |
The
Note that |
tarcieri
commented
Dec 15, 2025
So the problem is that |
newpavlov
commented
Dec 15, 2025
In a certain (uncharitable) sense, yes. But while |
tarcieri
commented
Dec 15, 2025
Perhaps I do still think something like this, internally within |
Not everyone needs RNG buffer zeroization. Introducing a crate feature for it is also not desirable. I think that zeroization should be handled on the I don't understand why you are against exposing the function. It a simple safe functon which can not be misused (as opposed to |
tarcieri
commented
Dec 15, 2025
I'm a bit worried about the notion that there's a user pulling in one crate which is expected to do some otherwise insecure zeroing, then separately pulling in Perhaps for a start you could use this technique as an internal replacement for |
Yes, it's unfortunate, but sometimes there is just no other way. Potentially unreliable erasure is better than no erasure at all. And it's much better than risking UB with Also note my point about efficiency. structFoo{a:[u8;32],b:[u64;16],c:u64,}implDropforFoo{fndrop(&mutself){// This impl is more efficientself.a = [0;32];self.b = [0u64;16];self.c = 0;
zeroize::observe(self);// than this implself.a.zeroize();self.b.zeroize();self.c.zeroize();}}See https://rust.godbolt.org/z/K7hxoePKE
Personally, I don't see much point it it, but sure. |
tarcieri
commented
Dec 15, 2025
The existing performance problems are already noted in #743. If you replaced |
newpavlov
commented
Dec 15, 2025
Well, IIUC even cc @RalfJung |
Uh oh!
There was an error while loading. Please reload this page.
observe functionatomic_fence with observeUh oh!
There was an error while loading. Please reload this page.
atomic_fence with observeatomic_fence with optimization_barriertarcieri
commented
Dec 15, 2025
Do you have more information about that? I wasn't aware compilers would change codegen based on an analysis of the ASM. |
I did not mean that existing compilers do it, but that AFAIK it's not explicitly forbidden. For example, an advanced LTO pass could in theory apply optimizations directly to generated assembly, thus removing "useless" writes to stack frame which immediately gets released. I tagged Ralf in the hope that he would clarify this moment in the case if I am mistaken. |
The details around
At the same time, the entire concept of zeroize is to try to do something that can't reliably be done in the Rust AM. From a formal opsem perspective, reliable zeroizing is impossible. (I wish it were different, but sadly that's where we are. Even if we wanted we couldn't do much about that in rustc until LLVM has proper support for this.) Best-effort zeroizing is a lot more about what compilers and optimizers actually do than about their specifications, and I'm the wrong person to ask about that. |
tarcieri
commented
Dec 15, 2025
@RalfJung what term would you use for this sort of tactical defense against code elimination that, at some future date, could be subject to another round of cat-and-mouse when the compiler outsmarts it? |
tarcieri
commented
Dec 15, 2025
I still think the best thing we could do with |
If we are to trust the compiler to not mess with |
RalfJung
commented
Dec 15, 2025
The paragraph you quoted was talking about reliable / sound reasoning principles. |
tarcieri
commented
Dec 15, 2025
I think we only need one implementation of memset/bzero per architecture. The implementation of zeroize itself is already abstracted such that there are two places to actually invoke it from: |
newpavlov
commented
Dec 15, 2025
tarcieri
commented
Dec 15, 2025
I’ve heard “optimization barrier” used for this by several people, though if that sounds like it has a property this isn’t providing, perhaps something like “optimization impediment” is clearer? |
RalfJung
commented
Dec 16, 2025
FWIW, I agree -- |
@RalfJung pubstructFoo{a:u64,}implDropforFoo{fndrop(&mutself){self.a = 0;unsafe{
core::arch::asm!("# {}",
in(reg)&self.a,
options(readonly, preserves_flags, nostack),);}}}implDropforFoo{fndrop(&mutself){unsafe{
core::arch::asm!("mov qword ptr [{}], 0",
in(reg)&mutself.a,
options(preserves_flags, nostack),);}}}Assuming that the compiler is forbidden from analyzing the |
RalfJung
commented
Dec 16, 2025
As I said above, that's outside my expertise and I recommend asking on the t-opsem Zulip. At least to a first-order approximation, I'd prefer the 2nd variant as that guarantees that the actual instructions you wrote there are in the final binary, so you don't even have to begin reasoning about what exactly the compiler may or may not do. |
Should this function be /// Observe the referenced data and prevent the compiler from removing previous writes to it./// ...fnoptimization_barrier<R: ?Sized>(val:&R){/* ... */}To quote from the The Rustonomicon:
The function states in its contract that it prevents the compiler from removing previous writes to it. There are two ways to interpret this:
My opinion is thus that /// Observe the referenced data and prevent the compiler from removing previous writes to it. The user must ensure the existence of previous writes./// ...unsafefnenforce_write<R: ?Sized>(val:&R){/* ... */} |
newpavlov
commented
Dec 18, 2025
It obviously should not. This function is essentially a "hardened" variant of |
dhardy
commented
Dec 18, 2025
|
newpavlov
commented
Dec 19, 2025
This guarantee has nothing to do with the notion of memory safety. |
c8ee20e to
1f03a14CompareUh oh!
There was an error while loading. Please reload this page.
| #[cfg(all( | ||
| not(miri), | ||
| any( | ||
| target_arch = "aarch64", | ||
| target_arch = "arm", | ||
| target_arch = "arm64ec", | ||
| target_arch = "loongarch64", | ||
| target_arch = "riscv32", | ||
| target_arch = "riscv64", | ||
| target_arch = "s390x", | ||
| target_arch = "x86", | ||
| target_arch = "x86_64", | ||
| ) | ||
| ))] |
There was a problem hiding this comment.
Would cfg! work here so you could just do if/else rather than duplicating the architecture list?
There was a problem hiding this comment.
It will not work since it would result in compilation of the asm! macro on targets which haven't stabilized it.
There was a problem hiding this comment.
Ideally, we would use cfg-if, but it's probably not worth to add a dependency just for it.
Hopefully, one day we will get a macro for this as part of core.
There was a problem hiding this comment.
cfg_select is currently undergoing stabilization :)
Uh oh!
There was an error while loading. Please reload this page.
The function was added in #1252.
## Added - `Zeroizing` is now `repr(transparent)` ([#1253]) - `optimization_barrier` function ([#1261]) - `Zeroizing` now supports `?Sized` ([#1318]) - `zeroize_stack` function ([#1331]) ## Changed - Edition changed to 2024 and MSRV bumped to 1.85 ([#1149]) - Replace `atomic_fence` with `optimization_barrier` ([#1252]) - Bump `zeroize_derive` to v1.5 ([#1492]) - Always enable AVX-512 support ([#1493]) [#1149]: #1149 [#1252]: #1252 [#1253]: #1253 [#1261]: #1261 [#1318]: #1318 [#1331]: #1331 [#1492]: #1492 [#1493]: #1493
No description provided.