Skip to content

zeroize: do not rely on volatile operations? #1269

Open

Description

@newpavlov

write_volatile docs explicitly state that:

Volatile operations are intended to act on I/O memory.

Using it on a non-volatile memory does not cause soundness issues, but it still can be considered a misuse of the function. It also can result in a very suboptimal codegen as noted in #743.

#1252 has introduced optimization_barrier which allows us to use simple writes optimizable by the compiler which are then "observed" by the barrier to prevent removal of the writes.

Unless we discover some practical issues with optimization_barrier, I think we can replace the volatile writes with simple writes. It would require changing promises made in the docs, but it should not matter in practice for downstream users.

As a potential compromise we could keep using volatile writes only on targets which do not have stable asm! macro and where optimization_barrier has to use the "hardened" black_box.

Activity

  1. tarcieri commented on Dec 24, 2025

    @tarcieri
    Member

    Unless we discover some practical issues with optimization_barrier

    This crate is advertised as providing guarantees: "Securely clear secrets from memory with a simple trait built on
    stable Rust primitives which guarantee memory is zeroed using an operation will not be 'optimized away' by the compiler."

    "Until we notice a problem" is not the same thing as a guarantee.

    I do not want to renege on the guarantees. IMO they're part of the v1 API contract, and I think removing guarantees would be a terrible motivation for a v2.

    I think there's an alternative to the volatile writes which also has guarantees and can improve performance: use asm! to perform the writes when available.

  2. tarcieri commented on Dec 24, 2025

    @tarcieri
    Member

    I also think you are over-obsessing about what is said in the Rust documentation about I/O memory. As is noted in the documentation and as we have discussed in the past, this usage has been vetted by several members of the Unsafe Code Working Group who did not see similar concerns.

    If you are truly concerned about this, I can look into getting the documentation modified to something like "The main intended use case for volatile operations is acting on I/O memory" or something which sounds less absolute.

  3. newpavlov commented on Dec 27, 2025

    @newpavlov
    MemberAuthor

    This crate is advertised as providing guarantees: "Securely clear secrets from memory with a simple trait built on
    stable Rust primitives which guarantee memory is zeroed using an operation will not be 'optimized away' by the compiler."

    In my understanding the optimization_barrier-based implementations also provide this guarantee (as much as it can be done with the current language), at the very least on targets with stable asm!. I believe that overwhelming majority of users do not care how exactly it's done, only that it's done robustly.

    Meanwhile, the volatile write approach has real issues with extremely poor codegen which would be cleanly resolved with optimization_barrier-based implementations.

    I think it's also important to decide whether we declare code like this as acceptable or not.

    this usage has been vetted by several members of the Unsafe Code Working Group who did not see similar concerns

    I haven't read the discussions, but I think it was "vetted" in the sense of "it does not cause UB", not in the sense "it's a good way to implement zeroization of secrets" and "it's an idiomatic use of volatile operations".

    I can look into getting the documentation modified to something like

    You could try, but I believe that the current docs are correct and should not be changed (i.e. my position is that volatile ops SHOULD NOT be used with non-IO memory), so I would disagree with such update.

  4. tarcieri commented on Dec 28, 2025

    @tarcieri
    Member

    In my understanding the optimization_barrier-based implementations also provide this guarantee (as much as it can be done with the current language)

    I'll begin by saying optimization_barrier has varying strategies: the asm!-based ones are more interesting, but the portable strategy uses only black_box as a defense which has no guarantees whatsoever. But let's start by discussing the asm!-based strategy.

    I will say this with the caveat that my understanding is when asm! is employed, what optimization_barrier is trying to do a common best practice, and one I'm excited we're supporting, but when I have discussed it in the past with compiler engineers, including on #1252, it's generally been with rather guarded language.

    We discussed a similar approach way back in 2021 on the RCIG issue tracker which was originally suggested by @chandlerc (where he or I called it value_fence).

    Every time I've discussed this approach with experts, it's been with caveats, more or less "good enough for now", but it's been suggested to me that future optimizers may be able to see through it. I am not the person to ask about how.

    I came to a similar conclusion based on our discussion in #1252, where to summarize @RalfJung and @dhardy there was concern about a lack of guarantees. 1, 2. @RalfJung specifically called it:

    a best-effort heuristic to steer the compiler away from things it shouldn't do

    Regarding your "as much as it can be done with the current language" comment, there are two things that really truly actually do provide a compiler-level guarantee writes will not be elided:

    1. volatile writes
    2. writes performed by asm!

    (there's another option in #1254 which is farming out the operation to various libc-specific APIs via FFI, but it's not one I'm particularly eager to pursue, and one that probably ends up in effect looking like optimization_barrier)

    my position is that volatile ops SHOULD NOT be used with non-IO memory

    That's correct for a RFC2119 interpretation of SHOULD, but it's not a MUST.

    IMO what's important is the semantics of volatile writes, versus it being an "off label" usage. Using them to express that we do not want the compiler to elide the writes is semantically correct for what we are trying to accomplish.


    What I'd prefer to do, at least initially, is ship optimization_barrier as a new feature. Users who want faster zeroization codegen can choose it as an alternative strategy when writing Zeroize impls.

    I'd be fine with making it the backing implementation for something like zeroize_flat_type which is an easy case to reason about.

    Maybe we could add an attribute/option for an alternative zeroize_derive codegen which uses it as an opt-in feature (e.g. writing Default::default() to every field, then observing).

    But before we make it the default strategy and relax the guarantees the crate currently provides in the name of performance, I'd like to see some additional scrutiny and reassurances that the specific implementation we're shipping, even when it's using asm! instead of black_box, is accomplishing the intended goals.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions