6 ms·
Rust looks interesting. 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 a
by AlyssaRowan 12y ago
Rust looks interesting.
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…?
- heinrich5991 12y agoYou 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.
- AlyssaRowan 12y agoI 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?
- MichaelGG 12y agoI know nothing about writing crypto core code. But... Rust supports inline assembly, so after you're done, you could always zeroize every register, right?
- astrange 12y agoSince 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.
- AlyssaRowan 12y agoGreat. Now your sandbox needs secure zeroisation. Yes, actually proving all parts of the compiler's data transforms going down to assembly kind of is what I'm after, if we can get it…
- deleted 12y ago[deleted]
- erickt 12y agoWe 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. [0]: http://www.meetup.com/Rust-Bay-Area/events/210632582/ http://www.meetup.com/Rust-Bay-Area/events/210632582/ [1]: https://air.mozilla.org/bay-area-rust-meetup-december-2014/ https://air.mozilla.org/bay-area-rust-meetup-december-2014/ [2]: https://github.com/seb-m/tars https://github.com/seb-m/tars
- pslam 12y agoThis 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.
- duaneb 12y agoWell, heartbleeed is a fiasco that would have been avoided by this—a vulnerability that rust shares without these secure destructors.
- mbrubeck 12y agoNo, 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]. [1]: http://www.tedunangst.com/flak/post/heartbleed-in-rust http://www.tedunangst.com/flak/post/heartbleed-in-rust [2]: https://news.ycombinator.com/item?id=8984169 https://news.ycombinator.com/item?id=8984169
- pslam 12y ago
- cesarb 12y agoIt 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).
- pekk 12y agoWhy can't you ensure it is zeroed in advance of destructor invocation?
- m_mueller 12y agobecause you don't know how 'intelligent' the compiler is. As long as there's no intrinsic you can't be sure that it works/keeps on working.
- omega_rythm 12y agoWhich 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.
- Animats 12y agoCloseout 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.