Wallet: Zero out wallet master key upon locking so it doesn't persist in memory#27080
Merged
achow101 merged 1 commit intobitcoin:masterfrom Feb 13, 2023
Merged
Conversation
Contributor
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
When an encrypted wallet is locked (for instance via the RPC `walletlock`), the docs indicate that the key is removed from memory. However, the vector (with a secure allocator) is merely cleared. This allows the key to persist indefinitely in memory. Instead, manually fill the bytes with zeroes before clearing.
d54ecff to
3a11adc
Compare
Contributor
|
Code review ACK 3a11adc |
Member
|
ACK 3a11adc |
sidhujag
pushed a commit
to syscoin/syscoin
that referenced
this pull request
Feb 14, 2023
…so it doesn't persist in memory 3a11adc Zero out wallet master key upon lock (John Moffett) Pull request description: When an encrypted wallet is locked (for instance via the RPC `walletlock`), the documentation indicates that the key is removed from memory: https://github.com/bitcoin/bitcoin/blob/b92d609fb25637ccda000e182da854d4b762eee9/src/wallet/rpc/encrypt.cpp#L157-L158 However, the vector (a `std::vector<unsigned char, secure_allocator<unsigned char>>`) is merely _cleared_. As it is a member variable, it also stays in scope as long as the wallet is loaded, preventing the secure allocator from deallocating. This allows the key to persist indefinitely in memory. I confirmed this behavior on my macOS machine by using an open-source third party memory inspector ("Bit Slicer"). I was able to find my wallet's master key in Bit Slicer after unlocking and re-locking my encrypted wallet. I then confirmed the key data was at the address in LLDB. This PR manually fills the bytes with zeroes before calling `clear()` by using our `memory_cleanse` function, which is designed to prevent the compiler from optimizing it away. I confirmed that it does remove the data from memory on my machine upon locking. Note: An alternative approach could be to call `vMasterKey.shrink_to_fit()` after the `clear()`, which would trigger the secure allocator's deallocation. However, `shrink_to_fit()` is not _guaranteed_ to actually change the vector's capacity, so I think it's unwise to rely on it. ## Edit: A little more clarity on why this is an improvement. Since `mlock`ed memory is guaranteed not to be swapped to disk and our threat model doesn't consider a super-user monitoring the memory in realtime, why is this an improvement? Most importantly, consider hibernation. Even `mlock`ed memory may get written to disk. From the `mlock` [manpage](https://man7.org/linux/man-pages/man2/mlock.2.html): > (But be aware that the suspend mode on laptops and some desktop computers will save a copy of the system's RAM to disk, regardless of memory locks.) As far as I can tell, this is true of [Windows](https://web.archive.org/web/20190127110059/https://blogs.msdn.microsoft.com/oldnewthing/20140207-00/?p=1833#:~:text=%5BThere%20does%20not%20appear%20to%20be%20any%20guarantee%20that%20the%20memory%20won%27t%20be%20written%20to%20disk%20while%20locked.%20As%20you%20noted%2C%20the%20machine%20may%20be%20hibernated%2C%20or%20it%20may%20be%20running%20in%20a%20VM%20that%20gets%20snapshotted.%20%2DRaymond%5D) and macOS as well. Therefore, a user with a strong OS password and a strong wallet passphrase could still have their keys stolen if a thief takes their (hibernated) machine and reads the permanent storage. ACKs for top commit: S3RK: Code review ACK 3a11adc achow101: ACK 3a11adc Tree-SHA512: c4e3dab452ad051da74855a13aa711892c9b34c43cc43a45a3b1688ab044e75d715b42843c229219761913b4861abccbcc8d5cb6ac54957d74f6e357f04e8730
Merged
Member
|
Added this to #26878 for backporting to 24.x. |
fanquake
pushed a commit
to fanquake/bitcoin
that referenced
this pull request
Feb 14, 2023
When an encrypted wallet is locked (for instance via the RPC `walletlock`), the docs indicate that the key is removed from memory. However, the vector (with a secure allocator) is merely cleared. This allows the key to persist indefinitely in memory. Instead, manually fill the bytes with zeroes before clearing. Github-Pull: bitcoin#27080 Rebased-From: 3a11adc
fanquake
pushed a commit
to fanquake/bitcoin
that referenced
this pull request
Feb 22, 2023
When an encrypted wallet is locked (for instance via the RPC `walletlock`), the docs indicate that the key is removed from memory. However, the vector (with a secure allocator) is merely cleared. This allows the key to persist indefinitely in memory. Instead, manually fill the bytes with zeroes before clearing. Github-Pull: bitcoin#27080 Rebased-From: 3a11adc
glozow
added a commit
that referenced
this pull request
Feb 27, 2023
784a754 wallet, rpc: Update migratewallet help text for encrypted wallets (Andrew Chow) debcfe3 tests: Tests for migrating wallets by name, and providing passphrase (Andrew Chow) ccc72fe wallet: Be able to unlock the wallet for migration (Andrew Chow) 50dd8b1 rpc: Allow users to specify wallet name for migratewallet (Andrew Chow) 648b062 wallet: Allow MigrateLegacyToDescriptor to take a wallet name (Andrew Chow) ab3bd45 i2p: use consistent number of tunnels with i2pd and Java I2P (Vasil Dimov) 29cdf42 i2p: lower the number of tunnels for transient sessions (Vasil Dimov) 5027e93 i2p: reuse created I2P sessions if not used (Vasil Dimov) a62c541 wallet: reuse change dest when recreating TX with avoidpartialspends (Matthew Zipkin) 64e7db6 Zero out wallet master key upon lock (John Moffett) b7e242e Correctly limit overview transaction list (John Moffett) cff6718 depends: fix systemtap download URL (fanquake) 7cf73df Add missing includes to fix gcc-13 compile error (MarcoFalke) 07397cd addrdb: Only call Serialize() once (Martin Zumsande) 91f83db hash: add HashedSourceWriter (Martin Zumsande) 5c824ac For feebump, ignore abandoned descendant spends (John Moffett) 428dcd5 wallet: Skip rescanning if wallet is more recent than tip (Andrew Chow) cbcdafa test: wallet: check that labels are migrated to watchonly wallet (Sebastian Falbesoner) 342abfb wallet: fully migrate address book entries for watchonly/solvable wallets (Sebastian Falbesoner) Pull request description: Backports: * #26595 * #26675 * #26679 * #26761 * #26837 * #26909 * #26924 * #26944 * bitcoin-core/gui#704 * #27053 * #27080 ACKs for top commit: instagibbs: ACK 784a754 achow101: ACK 784a754 hebasto: ACK 784a754, I've made backporting locally and got a diff between my branch and this PR as follows: Tree-SHA512: 8ea84aa02d7907ff1e202e1302b441ce9ed2198bf383620ad40056a5d7e8ea88e1047abef0b92d85648016bf9b3195c974be3806ccebd85bef4f85c326869e43
Fabcien
pushed a commit
to Bitcoin-ABC/bitcoin-abc
that referenced
this pull request
Mar 14, 2023
Summary: When an encrypted wallet is locked (for instance via the RPC `walletlock`), the docs indicate that the key is removed from memory. However, the vector (with a secure allocator) is merely cleared. This allows the key to persist indefinitely in memory. Instead, manually fill the bytes with zeroes before clearing. This is a backport of [[bitcoin/bitcoin#27080 | core#27080]] Test Plan: `ninja all check-all` Reviewers: #bitcoin_abc, Fabien Reviewed By: #bitcoin_abc, Fabien Differential Revision: https://reviews.bitcoinabc.org/D13327
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When an encrypted wallet is locked (for instance via the RPC
walletlock), the documentation indicates that the key is removed from memory:bitcoin/src/wallet/rpc/encrypt.cpp
Lines 157 to 158 in b92d609
However, the vector (a
std::vector<unsigned char, secure_allocator<unsigned char>>) is merely cleared. As it is a member variable, it also stays in scope as long as the wallet is loaded, preventing the secure allocator from deallocating. This allows the key to persist indefinitely in memory. I confirmed this behavior on my macOS machine by using an open-source third party memory inspector ("Bit Slicer"). I was able to find my wallet's master key in Bit Slicer after unlocking and re-locking my encrypted wallet. I then confirmed the key data was at the address in LLDB.This PR manually fills the bytes with zeroes before calling
clear()by using ourmemory_cleansefunction, which is designed to prevent the compiler from optimizing it away. I confirmed that it does remove the data from memory on my machine upon locking.Note: An alternative approach could be to call
vMasterKey.shrink_to_fit()after theclear(), which would trigger the secure allocator's deallocation. However,shrink_to_fit()is not guaranteed to actually change the vector's capacity, so I think it's unwise to rely on it.Edit: A little more clarity on why this is an improvement.
Since
mlocked memory is guaranteed not to be swapped to disk and our threat model doesn't consider a super-user monitoring the memory in realtime, why is this an improvement? Most importantly, consider hibernation. Evenmlocked memory may get written to disk. From themlockmanpage:As far as I can tell, this is true of Windows and macOS as well.
Therefore, a user with a strong OS password and a strong wallet passphrase could still have their keys stolen if a thief takes their (hibernated) machine and reads the permanent storage.