-
Notifications
You must be signed in to change notification settings - Fork 21
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
Add memory protections for secret values #108
Conversation
src/api.rs
Outdated
@@ -1042,20 +1052,38 @@ impl Hashable32 for PrivateKey { | |||
} | |||
} | |||
|
|||
/// Avoid accidental logging of secrets | |||
impl fmt::Debug for PrivateKey { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This stuff seems to have a different goal than memory protection, and I think we should have a debug here that is distinguishing in at least some way. Probably just don't want to have a Display
at all if the goal is to avoid accidental {}
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#110 is where we can discuss that further. This should be removed for now and we can keep this change non-breaking.
src/api.rs
Outdated
@@ -1042,20 +1052,38 @@ impl Hashable32 for PrivateKey { | |||
} | |||
} | |||
|
|||
/// Avoid accidental logging of secrets | |||
impl fmt::Debug for PrivateKey { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#110 is where we can discuss that further. This should be removed for now and we can keep this change non-breaking.
src/internal/memlock.rs
Outdated
// Which was generously released into the public domain. | ||
// The functions were modified to deal with structs rather than slices. | ||
|
||
#[cfg(all(unix, not(target_arch = "wasm32")))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file could benefit from use of https://docs.rs/cfg-if/0.1.10/cfg_if/, which would make it more clear which things are mutually exclusive of each other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty good to me, though I admit I don't understand all the details of the actual "memlock.rs", so others should review there more in depth
src/internal/memlock.rs
Outdated
cfg_if! { | ||
//If we are targeting wasm or not unix/windows we need to set the lock and unlock functions | ||
//to empty. This will also happen if you've enabled "disable_memlock" feature flag | ||
if #[cfg(any(feature="disable_memlock", target_arch = "wasm32", all(not(unix), not(windows))))] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all(not(unix), not(windows))
what target_family does this cover?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just a safety since we cove the others explicitly. If there were other target_famlies defined, the if would be true and the code would at least compile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mentioned this for Plaintext
, but I think it would be good to minimize the places that we are creating locks. Ideally we'd have one lock call and one unlock for each type. Drop
makes the unlock
side of that easy, but there are often multiple ways to create a type. Maybe there are performance reasons to not route everything through a single path, but we should consider it.
It might also be worth calling out somewhere in internal docs what types we do mlock for and why.
//If we are targeting wasm or not unix/windows we need to set the lock and unlock functions | ||
//to empty. This will also happen if you've enabled "disable_memlock" feature flag | ||
if #[cfg(any(feature="disable_memlock", target_arch = "wasm32", all(not(unix), not(windows))))] { | ||
#[inline(always)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this compiler hint makes sense as no code will be omitted for an empty function with no return.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't convinced that a function call to something empty wouldn't emit nothing, but I was convinced that inlining nothing would yield nothing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did look at the ASM for both and I couldn't ever make it emit any code for an empty function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's great if calls to these functions are no-op and have no code. These functions are needed to allow compile to work in certain cases and nothing more, right?
// The memlock code below is inspired by the secstr project | ||
// https://github.com/myfreeweb/secstr | ||
// Which was generously released into the public domain. | ||
// The functions were modified to deal with structs rather than slices. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like goal of the mlock code is two fold.
- Stop the marked memory from being swapped out
- Stop the marked memory from appearing in a core dump
Is this correct?
We also think this will work for Linux (including Android), *BSD (including OSX and iOS), and Windows?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, mlock is different on different platforms, but generally speaking, it just prevents memory from going to swap. On linux, madvise is what prevents parts of memory from going into a core dump.
mlock is supported on Linux and *BSD including OSX. I don't know for sure about iOS or Android, but I expect it would work fine there as libc should be available. For Windows, we use a winapi call of VirtualLock, which also just pins memory in memory and prevents swap. I've tested it on Mac, Linux, and Windows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some comments to the module that I hope reflects reality. Feel free to review.
I would like to see this building (and testing?) on windows before we merge it. We could add that to Travis or do it as part of #111. |
One thing that I didn't do that we should consider: check the return value from |
Benchmark results comparing with base:
|
@coltfred will you rebase this on master, please? |
- Remove sizeof_slice function when not necessary
see #107
These changes should prevent private keys from getting written to disk by the operating system. I went through and looked for any memory that we were zeroing out and added the protections there. All unit tests continue to work well.
Do we run unit tests in a Windows environment? If so, I'll attempt to add Windows support to this.
One ugly: in
memlock.rs
I have conditional compilation so this only happens on unix platforms, but there are empty no-op functions on non-unix. But I have to annotate each function with the conditional compilation. I initially usedmod memlock { ... }
and had two of those blocks each with an annotation. So then the file only had two of those, which was nice. But Clippy whined about inception. The use would beuse crate::internal::memlock::memlock
which is ugly. I couldn't find a way around this. Any ideas? I went for the thing I think is least ugly there.One other ugly: there's no way to call out to these functions without unsafe code. I don't believe it's dirty in this case; it just is what it is. We don't mutate the data values.