Merge bitcoin/bitcoin#28774: wallet: avoid returning a reference to vMasterKey after releasing the mutex that guards it

32a9f13cb8 wallet: avoid returning a reference to vMasterKey after releasing the mutex that guards it (Vasil Dimov)

Pull request description:

  `CWallet::GetEncryptionKey()` would return a reference to the internal
  `CWallet::vMasterKey`, guarded by `CWallet::cs_wallet`, which is unsafe.

  Returning a copy would be a shorter solution, but could have security
  implications of the master key remaining somewhere in the memory even
  after `CWallet::Lock()` (the current calls to
  `CWallet::GetEncryptionKey()` are safe, but that is not future proof).

  So, instead of `EncryptSecret(m_storage.GetEncryptionKey(), ...)`
  change the `GetEncryptionKey()` method to provide the encryption
  key to a given callback:
  `m_storage.WithEncryptionKey([](const CKeyingMaterial& k) { EncryptSecret(k, ...); })`

  This silences the following (clang 18):

  ```
  wallet/wallet.cpp:3520:12: error: returning variable 'vMasterKey' by reference requires holding mutex 'cs_wallet' [-Werror,-Wthread-safety-reference-return]
   3520 |     return vMasterKey;
        |            ^
  ```

  ---
  _Previously this PR modified both ArgsManager and wallet code. But the ArgsManager commit 856c88776f was merged in https://github.com/bitcoin/bitcoin/pull/29040 so now this only affects wallet code. The previous PR description was:_

  Avoid this unsafe pattern from `ArgsManager` and `CWallet`:

  ```cpp
  class A
  {
      Mutex mutex;
      Foo member GUARDED_BY(mutex);
      const Foo& Get()
      {
          LOCK(mutex);
          return member;
      } // callers of `Get()` will have access to `member` without owning the mutex.
  ```

ACKs for top commit:
  achow101:
    ACK 32a9f13cb8
  ryanofsky:
    Code review ACK 32a9f13cb8. This seems like a potentially real race condition, and the fix here is pretty simple.
  furszy:
    ACK 32a9f13c

Tree-SHA512: 133da84691642afc1a73cf14ad004a7266cb4be1a6a3ec634d131dca5dbcdef52522c1d5eb04f5b6c4e06e1fc3e6ac57315f8fe1e207b464ca025c2b4edefdc1
This commit is contained in:
Ava Chow 2024-01-23 14:50:58 -05:00
commit 6f732ffc3c
No known key found for this signature in database
GPG key ID: 17565732E08E5E41
4 changed files with 20 additions and 8 deletions

View file

@ -811,7 +811,9 @@ bool LegacyScriptPubKeyMan::AddKeyPubKeyInner(const CKey& key, const CPubKey &pu
std::vector<unsigned char> vchCryptedSecret;
CKeyingMaterial vchSecret{UCharCast(key.begin()), UCharCast(key.end())};
if (!EncryptSecret(m_storage.GetEncryptionKey(), vchSecret, pubkey.GetHash(), vchCryptedSecret)) {
if (!m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) {
return EncryptSecret(encryption_key, vchSecret, pubkey.GetHash(), vchCryptedSecret);
})) {
return false;
}
@ -997,7 +999,9 @@ bool LegacyScriptPubKeyMan::GetKey(const CKeyID &address, CKey& keyOut) const
{
const CPubKey &vchPubKey = (*mi).second.first;
const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
return DecryptKey(m_storage.GetEncryptionKey(), vchCryptedSecret, vchPubKey, keyOut);
return m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) {
return DecryptKey(encryption_key, vchCryptedSecret, vchPubKey, keyOut);
});
}
return false;
}
@ -2128,7 +2132,9 @@ std::map<CKeyID, CKey> DescriptorScriptPubKeyMan::GetKeys() const
const CPubKey& pubkey = key_pair.second.first;
const std::vector<unsigned char>& crypted_secret = key_pair.second.second;
CKey key;
DecryptKey(m_storage.GetEncryptionKey(), crypted_secret, pubkey, key);
m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) {
return DecryptKey(encryption_key, crypted_secret, pubkey, key);
});
keys[pubkey.GetID()] = key;
}
return keys;
@ -2262,7 +2268,9 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const
std::vector<unsigned char> crypted_secret;
CKeyingMaterial secret{UCharCast(key.begin()), UCharCast(key.end())};
if (!EncryptSecret(m_storage.GetEncryptionKey(), secret, pubkey.GetHash(), crypted_secret)) {
if (!m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) {
return EncryptSecret(encryption_key, secret, pubkey.GetHash(), crypted_secret);
})) {
return false;
}

View file

@ -22,6 +22,7 @@
#include <boost/signals2/signal.hpp>
#include <functional>
#include <optional>
#include <unordered_map>
@ -46,7 +47,8 @@ public:
virtual void UnsetBlankWalletFlag(WalletBatch&) = 0;
virtual bool CanSupportFeature(enum WalletFeature) const = 0;
virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr) = 0;
virtual const CKeyingMaterial& GetEncryptionKey() const = 0;
//! Pass the encryption key to cb().
virtual bool WithEncryptionKey(std::function<bool (const CKeyingMaterial&)> cb) const = 0;
virtual bool HasEncryptionKeys() const = 0;
virtual bool IsLocked() const = 0;
};

View file

@ -3513,9 +3513,10 @@ void CWallet::SetupLegacyScriptPubKeyMan()
AddScriptPubKeyMan(id, std::move(spk_manager));
}
const CKeyingMaterial& CWallet::GetEncryptionKey() const
bool CWallet::WithEncryptionKey(std::function<bool (const CKeyingMaterial&)> cb) const
{
return vMasterKey;
LOCK(cs_wallet);
return cb(vMasterKey);
}
bool CWallet::HasEncryptionKeys() const

View file

@ -962,7 +962,8 @@ public:
//! Make a LegacyScriptPubKeyMan and set it for all types, internal, and external.
void SetupLegacyScriptPubKeyMan();
const CKeyingMaterial& GetEncryptionKey() const override;
bool WithEncryptionKey(std::function<bool (const CKeyingMaterial&)> cb) const override;
bool HasEncryptionKeys() const override;
/** Get last block processed height */