One thing I'm not clear on it if can do, and that I'm interested in, is secure destructors.
Say I'm handling crypto, and I'm carting around an ephemeral key. When this goes out of scope, I definitely no matter what, want this zeroised by its destructor - as opposed to just having it (or a temporary copy made by a compiler optimisation!) zombling around the heap, stack or forgotten unused xmm registers because the compiler figured since I don't reference it again, the memory's contents are no longer important.
Current approaches to this involve explicit_bzero(), or other similar memset(0)-and-I-really-mean-it-don't-optimise-this-out techniques. (And a fair bit of testing and prayer when it comes to potential temporary copies or registers.) But unless you're doing it in assembly language, you don't really know. (The stack beneath you, such as the OS, any hypervisors, SMM, AMT, SGX, µcode etc, aside, of course!)
I'm not quite clear what Rust's behaviour with this scenario is. If it can do this easily, even potentially, I am very interested…?
We had a Bay Area Rust meetup [0] all about cryptography back in December (here[1] is a video if you want to watch the event). One of the subjects of that talk was about a project tars[2] that tries to provide for a secure buffer for keys that is protected against it being leaked using a variety of techniques. It hasn't yet been analyzed, so of course you shouldn't actually trust it yet.
There's a lot of interest in our community to build out a solid foundation and verified foundation of cryptography primitives. If you're interested in helping out, hop in the #rust-crypto channel on irc.mozilla.org.
This keeps coming up, but I think it's a very, very bad idea. It's false security.
If you are running in an environment where you don't trust code running in the same compartment/sandbox/process, then it's futile to zero out memory. The caller could have prepared things such that the memset doesn't work, if the key material went somewhere else.
If you ever find yourself thinking you need to do this, what you instead need is a helper process who's only purpose is to do primitive operations with sensitive key material.
Particularly as Rust is already a "safe" language - it doesn't even make sense to zero memory which by definition another piece of code can't access. Unless there's declared "unsafe" code lying around, but you wouldn't put that in the same process, would you? At which point, what are you even protecting against? If an in-process threat is that advanced, then you're not achieving anything.
Zeroing memory protects against future compromise. Sure, if you were already compromised, you already lost; but if you don't zero cryptographic material and you are compromised in the future (which can even be a physical attack like cold boot), you lose.
It's the same idea as in "forward secrecy": once the key material is discarded, it's gone, and no future compromise can bring it back. Being able to say "after this point in time these values don't exist anymore" is a powerful cryptographic primitive.
This: exactly this. Zeroisation is a fundamental cryptographic primitive, probably the most fundamental. Conceptually simple, but very easy to screw up, and has catastrophic consequences if the assumption is violated.
I want to be able to have an ephemeral secret, and then trust the code to do its very best to get rid of it when it's no longer needed. That doesn't mean just leave it rotting on the heap and promising it doesn't get accesed again, it means burning it to make sure. That's the underpinning of any possible proof of forward security.
Sure, you say, I want a helper process? Fine idea: compartmentation. Now that helper process needs secure zeroisation. And since I want it to be secure, surely I want to write that process in Rust. See where I'm going here?
Whether 'safe' code I trust within my environment can read it again is totally irrelevant, if the machine later gets rooted or booted, nonstopped or whatever.
Zeroing out memory after use is mitigation against security flaws like heart bleed. It's not a security feature in it self. Although you are right that the secure process is probably just the better solution anyway.
It's sort of like the arguments for DRM. Of course pirates will always break it, but we can still mitigate it in a number of ways.
Not false sense of security: suspending VMs to disk. The contents of previously used, free memory that has not been reused by the kernel are written to disk. Now it has a lifetime far longer than RAM.
So you're essentially trying to protect against a hypervisor attack? That's never going to work unless you put the primitives in the hypervisor. Assuming you own it.
No, it wouldn't. For example, the "Heartbleed in Rust" blog post [1] re-used a buffer without freeing it. No destructor runs in between the two uses, so a zeroing destructor could not possibly prevent the bug.
Maybe zeroing destructors make sense as defense-in-depth, but I don't see how they can fix a Heartbleed-style exploit in Rust. In code where the buffer is freed and its destructor runs, Rust's memory safety guarantees already prevent it from being accessed after free. In vulnerable code that just uses the same buffer twice, the destructor never has a chance to run so its behavior doesn't matter.
The real Heartbleed vulnerability (CVE-2014-0160 in OpenSSL) involved reading into uninitialized memory in a newly-allocated buffer, which safe Rust code already prevents [2].
Thanks - that's a good example of what I was trying to convey.
The point is Rust already provides safety guarantees. If you don't trust the runtime, then why would you trust the built-in zero'ing? I get the "defense in depth" argument, but it feels a bit like doing this:
{
int a = secret; // Get secret.
assert(a == secret); // Check "a" is actually that.
a = 0; // Ensure "a" is zero'd on exit.
assert(a == 0); // Just because.
}
And yes, I get that you can build this into the language so it's not quite as ridiculous - you actually wipe tainted stack, for example.
But the point is: the runtime has an ABI and a machine model. Information is allowed to leak across function boundaries, beacuse it doesn't matter. Without using the "unsafe" keyword, there are no methods of getting around the machine model and dipping into the underlying actual machine.
Even if you don't have a "safe" language and runtime, it's still of limited value. It protects against threats involving data or control flow corruption after key usage, and where there isn't sufficient control of the program to perturb the secret-consuming functions. That's more of an annoyance than prevention. On the other hand, it gave the programmer a false sense that it was properly wiping secrets.
It is very probable that a sufficiently smart optimizer could see the assertion was always true and delete it. Then see no one reads "a" and delete it as well. In certain circumstances this can cause a secret to be leaked in, say a register, making our safe function unsafe. You need to be very careful writing secure code, and probably need to go down to the level of writing assembly to be sure the optimizer isn't turning your safe code into unsafe code.
We actually have an interesting project in rust where someone is writing a syntax extension to take rust like code and generate assembly [0]. It's probably unsafe to use right now but if sufficiently well implemented it could be the foundation of a lot of interesting cryptography work.
Sorry, I wasn't clear enough that my code was intended as sarcastic. It's obviously silly to zero variables because the compiler is free to ignore you. The point is, the underlying machine is going to do the same.
There are many ways to dig out stale memory if you're running at sufficient privilege, for example. Direct cache introspection, for example, or bypass. Zero-izing alone is not sufficiently strong to mitigate the threats people imagine it works against.
The heartbleed "issue" in Rust involved code that passed the same array to two functions, making it apparent that the array was never getting destroyed. Since they used a custom memory allocator, secure destructors won't help you since your array might not even get destroyed. Lessons: Don't use custom allocators.
You need to put the key material into an opaque struct, which does store it on the heap. Using the `black_box` function you can zero the key material out in the destructor.
I see. Which would be kind of similar to how you do it in C; the destructor is actually guaranteed to run when it goes out-of-scope?
But what I'm a bit more worried about is how the secret data gets in there, and what happens while I'm working with it: expansion, cipher state, key setup, all the little adds and xors and rots (dammit, why doesn't ROT ever get some real operator love? It's got first-class instructions… :() - all that stuff you'd do in u32 and u64. Temporary copies may still be a problem, if you look at the object that actually comes out of compilers sometimes.
Does using an opaque type actually deal with that issue here?
I know nothing about writing crypto core code. But... Rust supports inline assembly, so after you're done, you could always zeroize every register, right?
Since the compiler can do whatever it wants, you can't be sure it hasn't put any part of the key anywhere on the stack, and there isn't sufficient information for the asm to know which registers contain tainted values.
Unless you can actually prove all parts of the compiler's data transforms going down to assembly I think the safest thing to do is sandbox your key-handling process so nobody else can examine it.
It would be good to have a "secure" type modifier which tells the compiler "once a value with this type is dead, immediately overwrite it with garbage" (instead of the normal behavior of just leaving it around until the register/stack slot/memory area is needed for something else).
Closeout is always difficult. Destructors don't handle errors well. Neither does Go's "defer".
Python's "with" clause seems to do the best job of making sure closeout is handled properly. Take a careful look at the extra arguments to a __exit__ function in Python,s "with". It's one of the few closeout methods where things such as an exception during closeout (an I/O error during file close, for example) is handled in a way that doesn't interfere with other closeouts.
Which is a deficiency of compilers and languages they support: there should be a notion of "verbatim" code, where the compiler doesn't try to apply any code transformation changing the "literal" semantics (as opposed to "intended" semantics).
Of course this would only be possible for languages like C which don't make any advanced abstractions on the hardware arch.
One thing I'm not clear on it if can do, and that I'm interested in, is secure destructors.
Say I'm handling crypto, and I'm carting around an ephemeral key. When this goes out of scope, I definitely no matter what, want this zeroised by its destructor - as opposed to just having it (or a temporary copy made by a compiler optimisation!) zombling around the heap, stack or forgotten unused xmm registers because the compiler figured since I don't reference it again, the memory's contents are no longer important.
Current approaches to this involve explicit_bzero(), or other similar memset(0)-and-I-really-mean-it-don't-optimise-this-out techniques. (And a fair bit of testing and prayer when it comes to potential temporary copies or registers.) But unless you're doing it in assembly language, you don't really know. (The stack beneath you, such as the OS, any hypervisors, SMM, AMT, SGX, µcode etc, aside, of course!)
I'm not quite clear what Rust's behaviour with this scenario is. If it can do this easily, even potentially, I am very interested…?