Skip to content

Commit 75021e8

Browse files
committed
Merge #18487: rpc: Fix rpcRunLater race in walletpassphrase
7b8e157 rpc: Fix rpcRunLater race in walletpassphrase (João Barbosa) Pull request description: Release locks before calling `rpcRunLater`. Quick explanation: `rpcRunLater` leads to `event_free` which calls `event_del` which can wait for the event callback to finish if it's already running and that callback will try to lock wallet mutex - which is already locked in http thread. Fixes #14995 , fixes #18482. Best reviewed with whitespace changes hidden. ACKs for top commit: MarcoFalke: ACK 7b8e157, only tested that this avoids the node freezing. Did not look at how libevent works or how the deadlock happens or if this breaks other stuff. 📞 ryanofsky: Code review ACK 7b8e157. Just updated comment since last review Tree-SHA512: 17874a2fa7b0e164fb0d7ee4cb7d59650275b8c03476fb291d60af8b758495457660d3912623fb26259fefe84aeba21c0a9e0c6467982ba511f19344ed5413ab
2 parents 54d5ba3 + 7b8e157 commit 75021e8

File tree

1 file changed

+39
-31
lines changed

1 file changed

+39
-31
lines changed

src/wallet/rpcwallet.cpp

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,44 +1912,52 @@ static UniValue walletpassphrase(const JSONRPCRequest& request)
19121912
},
19131913
}.Check(request);
19141914

1915-
auto locked_chain = pwallet->chain().lock();
1916-
LOCK(pwallet->cs_wallet);
1917-
1918-
if (!pwallet->IsCrypted()) {
1919-
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrase was called.");
1920-
}
1915+
int64_t nSleepTime;
1916+
{
1917+
auto locked_chain = pwallet->chain().lock();
1918+
LOCK(pwallet->cs_wallet);
19211919

1922-
// Note that the walletpassphrase is stored in request.params[0] which is not mlock()ed
1923-
SecureString strWalletPass;
1924-
strWalletPass.reserve(100);
1925-
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
1926-
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
1927-
strWalletPass = request.params[0].get_str().c_str();
1920+
if (!pwallet->IsCrypted()) {
1921+
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrase was called.");
1922+
}
19281923

1929-
// Get the timeout
1930-
int64_t nSleepTime = request.params[1].get_int64();
1931-
// Timeout cannot be negative, otherwise it will relock immediately
1932-
if (nSleepTime < 0) {
1933-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative.");
1934-
}
1935-
// Clamp timeout
1936-
constexpr int64_t MAX_SLEEP_TIME = 100000000; // larger values trigger a macos/libevent bug?
1937-
if (nSleepTime > MAX_SLEEP_TIME) {
1938-
nSleepTime = MAX_SLEEP_TIME;
1939-
}
1924+
// Note that the walletpassphrase is stored in request.params[0] which is not mlock()ed
1925+
SecureString strWalletPass;
1926+
strWalletPass.reserve(100);
1927+
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
1928+
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
1929+
strWalletPass = request.params[0].get_str().c_str();
1930+
1931+
// Get the timeout
1932+
nSleepTime = request.params[1].get_int64();
1933+
// Timeout cannot be negative, otherwise it will relock immediately
1934+
if (nSleepTime < 0) {
1935+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative.");
1936+
}
1937+
// Clamp timeout
1938+
constexpr int64_t MAX_SLEEP_TIME = 100000000; // larger values trigger a macos/libevent bug?
1939+
if (nSleepTime > MAX_SLEEP_TIME) {
1940+
nSleepTime = MAX_SLEEP_TIME;
1941+
}
19401942

1941-
if (strWalletPass.empty()) {
1942-
throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase can not be empty");
1943-
}
1943+
if (strWalletPass.empty()) {
1944+
throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase can not be empty");
1945+
}
19441946

1945-
if (!pwallet->Unlock(strWalletPass)) {
1946-
throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect.");
1947-
}
1947+
if (!pwallet->Unlock(strWalletPass)) {
1948+
throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect.");
1949+
}
19481950

1949-
pwallet->TopUpKeyPool();
1951+
pwallet->TopUpKeyPool();
19501952

1951-
pwallet->nRelockTime = GetTime() + nSleepTime;
1953+
pwallet->nRelockTime = GetTime() + nSleepTime;
1954+
}
19521955

1956+
// rpcRunLater must be called without cs_wallet held otherwise a deadlock
1957+
// can occur. The deadlock would happen when RPCRunLater removes the
1958+
// previous timer (and waits for the callback to finish if already running)
1959+
// and the callback locks cs_wallet.
1960+
AssertLockNotHeld(wallet->cs_wallet);
19531961
// Keep a weak pointer to the wallet so that it is possible to unload the
19541962
// wallet before the following callback is called. If a valid shared pointer
19551963
// is acquired in the callback then the wallet is still loaded.

0 commit comments

Comments
 (0)