Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zero memory on drop #87

Closed
fxha opened this issue Jul 22, 2019 · 22 comments · Fixed by #545
Closed

Zero memory on drop #87

fxha opened this issue Jul 22, 2019 · 22 comments · Fixed by #545

Comments

@fxha
Copy link

fxha commented Jul 22, 2019

I have a question about the hash memory management but also for other related repositories. As it seems RustCrypto use GenericArray and/or raw arrays for sensitive data like keys and internal buffers but the memory is not overwritten/zeroed on reset or when the buffer/struct is dropped for most crates, right? Are there any plans to change this?

@newpavlov
Copy link
Member

Yes, you are right, currently most crates do not zeroize inner state on drop. IIUC to implement it without hacks we will need either generic-array support (e.g. by implementing Zeroize trait.) or const generics.

@tarcieri
Copy link
Member

tarcieri commented Jul 22, 2019

@newpavlov you can do a Zeroize impl without explicit support for generic-array by borrowing the contents of the latter as a byte slice and calling zeroize() on that. IterMut will also work.

@newpavlov
Copy link
Member

@tarcieri Maybe we can transform the whole hash state into byte slice (using unsafe code) and call zeroize on it?

@tarcieri
Copy link
Member

tarcieri commented Jul 22, 2019

@newpavlov or you can just call zeroize on each value in its state in a Drop handler. zeroize is designed to support structured data. If they're GenericArray you just need to call as_ref() (or as_slice()?) first iirc

When const generics land it should be possible to derive a Zeroize impl and drop handler using zeroize_derive (the same thing would be possible with first-class generic-array support for zeroize, but that doesn't exist).

@tarcieri
Copy link
Member

@fxha is there a particular hash function or set of them you'd specifically like to have this in?

We can add optional zeroize features to high-priority crates.

@fxha
Copy link
Author

fxha commented Jun 10, 2020

I think it's good practice to (optionally) erase secret data and don't leak keys in memory.

@tarcieri
Copy link
Member

tarcieri commented Jun 11, 2020

I agree (in fact I wrote the most popular crate for it).

However hash functions are a bit of an odd fish in this regard. Ones based on the Merkle–Damgård construction (which constitute a large number of the ones in this repo, including the popular sha2) effectively output their internal state upon finalization (which is what enables length extension attacks). If that internal state were reversible to a secret, that would be a preimage attack against the underlying hash function.

I think the hash functions where this would be relevant are, as you pointed out, ones with keys, i.e. ones which provide PRFs.

As far as I know the only one in this particular repo that applies to is blake2.

@burdges
Copy link

burdges commented Jun 11, 2020

Zeroize cannot prevent the type from being used after zeroing. Internal states often include constants, so zeroizing can leave an internal state that functions but provides less security.

@fxha
Copy link
Author

fxha commented Jun 11, 2020

@tarcieri I don't quite remember, but I was using Blake2 and noticed that my key was copied or something like this. Cleaning the internal state isn't a high priority but basic cleanup should be provided.

cbeck88 added a commit to cbeck88/generic-array that referenced this issue Feb 10, 2021
zeroize is the most popular crate for zeroizing memory after use.
generic-array is the most popular crate for representing bytes in
cryptographic implementations which can't use an allocator.

Issues have been opened in RustCrypto about zeroizing instances of
generic-array, for instance here:
RustCrypto/hashes#87

However, sometimes you want to return generic-array from an API
while also commiting it to be zeroized after use, because the caller
might forget to do this on some code path. The natural way to do that
in zeroize crate is the `Zeroizing` wrapper. However, `Zeroizing`
cannot be used with `generic-array` unless `generic-array` implements
the `Zeroize` trait.

The easiest way to do that is create an optional dependency on
`Zeroize` and put the implementation in a conditionally-compiled
module, as we did in this commit.
@newpavlov
Copy link
Member

newpavlov commented Feb 10, 2021

I wonder if we should make the reset capability optional... It would allow us to remove the caching of initial state and thus secret key inside hasher state will be overwritten as soon as first block of data has been processed. How often in your opinion users need to reset MAC states?

@koraa
Copy link

koraa commented Jan 14, 2023

How about adding the Zeroize trait for blake2 at least? I am trying to implement a noise-like protocol where hmac-blake2 is used as a KDF. Right now I am using libsodium including sodium_malloc/free to make sure ephemeral secrecy is guaranteed.

The lack of well thought-through zeroization in RustCrypto is a major blocker for moving away from sodium.

@tarcieri
Copy link
Member

tarcieri commented Jan 14, 2023

I think an optional zeroize feature which adds a Drop impl that zeroizes the relevant state is probably the best approach, along with a ZeroizeOnDrop impl.

The problem with Zeroize is it might leave the hasher in an invalid state without consuming it.

@laudiacay
Copy link

#449

@mmaker
Copy link

mmaker commented Jul 2, 2023

Hi! bumping this again because a lot of project effectively need digest::Digest to implement zeroize::Zeroize. Do you need help with this?

@kayabaNerve
Copy link

  1. Impl Zeroize for Digest such that after zeroize(), it writes the valid state. If the Zeroize impl is ordered with respect to the post-write, then this will leave the hasher in a valid state. While this post-write may be optimized out if unused, if reused, it should be valid. No?

(I'm not sure this is valid due to the ordering pre-condition. Doesn't Zeroize use a volatile only guaranteed to be ordered with regard to other volatiles? If so, I think that'd make all use of Zeroize unsafe, so I doubt it, but I obviously may be missing something...)

  1. I would love Zeroize on all items satisfying Digest because the resulting state is a secret. It's used as a nonce in a variety of systems. This leaves me to flood Digest with block size of data (if a block based hash), attempting to overwrite the prior state, and then get the resulting Digest marked used (to prevent the block of data written from being optimized out).

@tarcieri
Copy link
Member

Doesn't Zeroize use a volatile only guaranteed to be ordered with regard to other volatiles?

You are thinking of some outdated guidelines. Please read this section of the zeroize documentation:

https://docs.rs/zeroize/latest/zeroize/#what-guarantees-does-this-crate-provide

@kayabaNerve
Copy link

In that case, the sole question is if a further write is guaranteed to be ordered, and if not, how to trigger a read it is ordered in regard to (not my field of expertise). Regardless, sounds like my proposed solution could/should work?

@tarcieri
Copy link
Member

tarcieri commented Sep 19, 2023

I would probably suggest just adding a Drop impl, rather than a Zeroize impl (you could also add a ZeroizeOnDrop impl).

The problem with adding a Zeroize impl is there's a potential "use-after-zeroize" condition that can occur, which can't occur with a Drop impl.

Edit: oops, I already said this

@kayabaNerve
Copy link

Right, I read the history, yet if after the zeroize we re-write the starting state... it's use-after-init-after-zeroize. That shouldn't introduce any unsafety.

@tarcieri
Copy link
Member

It isn't a "safety" issue in the Rust sense, but the problem is zeroize might be called on it, then it could be reused in a context where the zeroization was unexpected.

Though I suppose there's an argument to be made that it's no different from Reset::reset, and adding a Zeroize impl would not penalize users who don't care about zeroization.

@kayabaNerve
Copy link

If a user explicitly calls Zeroize, which is just a stronger reset in this context, and then it's reused 'unexpectedly', I'd argue that's the users fault, yes.

@newpavlov
Copy link
Member

it's use-after-init-after-zeroize. That shouldn't introduce any unsafety.

It's a logical bug. Zeroized state may not be valid for a given hash function. In the worst case scenario, it may even break safety assumptions used by implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants