Skip to content

Commit 493b813

Browse files
committed
wallet: ensure that the passphrase is not deleted from memory when being used to rescan
`m_relock_mutex` is introduced so that the passphrase is not deleted from memory when the timeout provided in `walletpassphrase` is up, but the wallet is still rescanning.
1 parent 66a86eb commit 493b813

File tree

5 files changed

+18
-10
lines changed

5 files changed

+18
-10
lines changed

src/wallet/rpc/backup.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1655,6 +1655,10 @@ RPCHelpMan importdescriptors()
16551655
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
16561656
}
16571657

1658+
// Ensure that the wallet is not locked for the remainder of this RPC, as
1659+
// the passphrase is used to top up the keypool.
1660+
LOCK(pwallet->m_relock_mutex);
1661+
16581662
const UniValue& requests = main_request.params[0];
16591663
const int64_t minimum_timestamp = 1;
16601664
int64_t now = 0;

src/wallet/rpc/encrypt.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ RPCHelpMan walletpassphrase()
9090
std::weak_ptr<CWallet> weak_wallet = wallet;
9191
pwallet->chain().rpcRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet, relock_time] {
9292
if (auto shared_wallet = weak_wallet.lock()) {
93-
LOCK(shared_wallet->cs_wallet);
93+
LOCK2(shared_wallet->m_relock_mutex, shared_wallet->cs_wallet);
9494
// Skip if this is not the most recent rpcRunLater callback.
9595
if (shared_wallet->nRelockTime != relock_time) return;
9696
shared_wallet->Lock();
@@ -122,8 +122,6 @@ RPCHelpMan walletpassphrasechange()
122122
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
123123
if (!pwallet) return UniValue::VNULL;
124124

125-
LOCK(pwallet->cs_wallet);
126-
127125
if (!pwallet->IsCrypted()) {
128126
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called.");
129127
}
@@ -132,6 +130,8 @@ RPCHelpMan walletpassphrasechange()
132130
throw JSONRPCError(RPC_WALLET_ERROR, "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before changing the passphrase.");
133131
}
134132

133+
LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet);
134+
135135
// TODO: get rid of these .c_str() calls by implementing SecureString::operator=(std::string)
136136
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
137137
SecureString strOldWalletPass;
@@ -179,8 +179,6 @@ RPCHelpMan walletlock()
179179
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
180180
if (!pwallet) return UniValue::VNULL;
181181

182-
LOCK(pwallet->cs_wallet);
183-
184182
if (!pwallet->IsCrypted()) {
185183
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called.");
186184
}
@@ -189,6 +187,8 @@ RPCHelpMan walletlock()
189187
throw JSONRPCError(RPC_WALLET_ERROR, "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before locking the wallet.");
190188
}
191189

190+
LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet);
191+
192192
pwallet->Lock();
193193
pwallet->nRelockTime = 0;
194194

@@ -227,8 +227,6 @@ RPCHelpMan encryptwallet()
227227
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
228228
if (!pwallet) return UniValue::VNULL;
229229

230-
LOCK(pwallet->cs_wallet);
231-
232230
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
233231
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: wallet does not contain private keys, nothing to encrypt.");
234232
}
@@ -241,6 +239,8 @@ RPCHelpMan encryptwallet()
241239
throw JSONRPCError(RPC_WALLET_ERROR, "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before encrypting the wallet.");
242240
}
243241

242+
LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet);
243+
244244
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
245245
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
246246
SecureString strWalletPass;

src/wallet/rpc/transactions.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,7 @@ RPCHelpMan rescanblockchain()
880880
std::optional<int> stop_height;
881881
uint256 start_block;
882882

883+
LOCK(pwallet->m_relock_mutex);
883884
{
884885
LOCK(pwallet->cs_wallet);
885886
EnsureWalletIsUnlocked(*pwallet);

src/wallet/wallet.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase,
551551
bool fWasLocked = IsLocked();
552552

553553
{
554-
LOCK(cs_wallet);
554+
LOCK2(m_relock_mutex, cs_wallet);
555555
Lock();
556556

557557
CCrypter crypter;
@@ -786,7 +786,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
786786
return false;
787787

788788
{
789-
LOCK(cs_wallet);
789+
LOCK2(m_relock_mutex, cs_wallet);
790790
mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
791791
WalletBatch* encrypted_batch = new WalletBatch(GetDatabase());
792792
if (!encrypted_batch->TxnBegin()) {
@@ -3407,7 +3407,7 @@ bool CWallet::Lock()
34073407
return false;
34083408

34093409
{
3410-
LOCK(cs_wallet);
3410+
LOCK2(m_relock_mutex, cs_wallet);
34113411
if (!vMasterKey.empty()) {
34123412
memory_cleanse(vMasterKey.data(), vMasterKey.size() * sizeof(decltype(vMasterKey)::value_type));
34133413
vMasterKey.clear();

src/wallet/wallet.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,9 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
488488

489489
// Used to prevent concurrent calls to walletpassphrase RPC.
490490
Mutex m_unlock_mutex;
491+
// Used to prevent deleting the passphrase from memory when it is still in use.
492+
RecursiveMutex m_relock_mutex;
493+
491494
bool Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys = false);
492495
bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase);
493496
bool EncryptWallet(const SecureString& strWalletPassphrase);

0 commit comments

Comments
 (0)