Wipe memory reliably in Memory::Zero#1816
Conversation
|
Thank you for looking into this. I can't merge this PR as-is. In particular, the statement that this is not on a bulk path is not correct. During non quick volume creation, For me, the right approach is to introduce a dedicated secure erasure API, e.g. So I agree with the goal, but not with changing |
| // buffer that is not read afterwards can be removed as a dead store, | ||
| // leaving secrets in memory. burn() is the same volatile-write wipe | ||
| // already used by Buffer::Erase(). This is not on the bulk I/O path. | ||
| burn (memory, size); |
There was a problem hiding this comment.
I don't think Memory::Zero should call burn globally. This function is a generic zero fill primitive and is used outside secret erasure contexts, including repeated large buffer paths such as non quick volume creation via outputBuffer.Zero in VolumeCreator.cpp. Please introduce a dedicated secure erasure API instead, use it from BufferPtr::Erase, and leave generic Zero as normal zero initialization.
BufferPtr::Erase() was an alias for Zero(), i.e. a plain memset(), which the compiler may remove as a dead store when the buffer is not read afterwards. Every current BufferPtr::Erase() call site wipes secrets -- typed passwords (TextUserInterface, VolumePasswordPanel) and security token keyfile data (TextUserInterface, SecurityTokenKeyfilesDialog) -- so an elided wipe can leave them in memory. Introduce a dedicated secure erasure primitive, Memory::SecureErase(), implemented with the same volatile-write burn() loop that Buffer::Erase() already uses, and route both BufferPtr::Erase() and Buffer::Erase() through it. Memory::Zero() remains a plain memset() for generic zero initialization, so bulk zero-fill paths such as VolumeCreator's outputBuffer.Zero() before encryption are unaffected. A case-by-case review of the remaining call sites found no other changes needed: all .Zero() calls in the tree are genuine zero-initialization (header buffers before serialization, keyfile pool padding, RNG self-test pool reset, FAT formatter scratch sectors, POD struct init), and all .Erase() calls are genuine wipes, which now reach burn() in every case.
ca467fa to
4539200
Compare
|
Thanks — that's a fair objection, and you're right that the "not on a bulk path" claim was wrong: I've reworked this along the lines you suggested. Going through the call sites case by case, this turned out to fix a real gap rather than just harden one: every Rebased onto current master. Builds clean (C++03 and C++11); |
Memory::Zero() used a plain memset(), which the compiler may remove as a dead store when the buffer is not read afterwards. Memory::Zero backs BufferPtr::Erase() and BufferPtr::Zero(), which are used to clear key material and other secrets, so an elided memset can leave secrets in memory.
Use burn() instead — the same volatile-write wipe already used by Buffer::Erase(). Memory::Zero is not called on the bulk encryption/I/O path, so this has no throughput impact.