Skip to content

Commit

Permalink
Merge bitcoin#18814: rpc: Relock wallet only if most recent callback
Browse files Browse the repository at this point in the history
9f59dde rpc: Relock wallet only if most recent callback (João Barbosa)
a2e6db5 rpc: Add mutex to guard deadlineTimers (João Barbosa)

Pull request description:

  This PR fixes an early relocking race condition from bitcoin#18811 where old relock callback runs after new wallet unlock code and nRelockTime update but before rpcRunLater call, causing early relock and incorrect nRelockTime time

  Issue introduced in bitcoin#18487.
  Fixes bitcoin#18811.

ACKs for top commit:
  MarcoFalke:
    ACK 9f59dde
  ryanofsky:
    Code review ACK 9f59dde. No changes since last review except squashing commits.
  jonatack:
    ACK 9f59dde

Tree-SHA512: 2f7fc03e5ab6037337f2d82dfad432495cc337c77d07c968ee2355105db6292f24543c03456f5402e0e759577a4327758f9372f7ea29de6d56dc3695fda9b379
  • Loading branch information
fanquake authored and vijaydasmp committed Nov 25, 2023
1 parent 9547d18 commit f4ced33
Show file tree
Hide file tree
Showing 3 changed files with 14 additions and 4 deletions.
6 changes: 4 additions & 2 deletions src/rpc/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ static std::string rpcWarmupStatus GUARDED_BY(g_rpc_warmup_mutex) = "RPC server
/* Timer-creating functions */
static RPCTimerInterface* timerInterface = nullptr;
/* Map of name to timer. */
static std::map<std::string, std::unique_ptr<RPCTimerBase> > deadlineTimers;
static Mutex g_deadline_timers_mutex;
static std::map<std::string, std::unique_ptr<RPCTimerBase> > deadlineTimers GUARDED_BY(g_deadline_timers_mutex);
static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler, std::multimap<std::string, std::vector<UniValue>> mapPlatformRestrictions);

// Any commands submitted by this user will have their commands filtered based on the mapPlatformRestrictions
Expand Down Expand Up @@ -330,7 +331,7 @@ void InterruptRPC()
void StopRPC()
{
LogPrint(BCLog::RPC, "Stopping RPC\n");
deadlineTimers.clear();
WITH_LOCK(g_deadline_timers_mutex, deadlineTimers.clear());
DeleteAuthCookie();
g_rpcSignals.Stopped();
}
Expand Down Expand Up @@ -609,6 +610,7 @@ void RPCRunLater(const std::string& name, std::function<void()> func, int64_t nS
{
if (!timerInterface)
throw JSONRPCError(RPC_INTERNAL_ERROR, "No timer handler registered for RPC");
LOCK(g_deadline_timers_mutex);
deadlineTimers.erase(name);
LogPrint(BCLog::RPC, "queue run of timer %s in %i seconds (using %s)\n", name, nSeconds, timerInterface->Name());
deadlineTimers.emplace(name, std::unique_ptr<RPCTimerBase>(timerInterface->NewTimer(func, nSeconds*1000)));
Expand Down
8 changes: 7 additions & 1 deletion src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1927,6 +1927,9 @@ static UniValue walletpassphrase(const JSONRPCRequest& request)
CWallet* const pwallet = wallet.get();

int64_t nSleepTime;
int64_t relock_time;
// Prevent concurrent calls to walletpassphrase with the same wallet.
LOCK(pwallet->m_unlock_mutex);
{
LOCK(pwallet->cs_wallet);

Expand Down Expand Up @@ -1975,6 +1978,7 @@ static UniValue walletpassphrase(const JSONRPCRequest& request)
pwallet->TopUpKeyPool();

pwallet->nRelockTime = GetTime() + nSleepTime;
relock_time = pwallet->nRelockTime;

}
// rpcRunLater must be called without cs_wallet held otherwise a deadlock
Expand All @@ -1986,9 +1990,11 @@ static UniValue walletpassphrase(const JSONRPCRequest& request)
// wallet before the following callback is called. If a valid shared pointer
// is acquired in the callback then the wallet is still loaded.
std::weak_ptr<CWallet> weak_wallet = wallet;
pwallet->chain().rpcRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet] {
pwallet->chain().rpcRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet, relock_time] {
if (auto shared_wallet = weak_wallet.lock()) {
LOCK(shared_wallet->cs_wallet);
// Skip if this is not the most recent rpcRunLater callback.
if (shared_wallet->nRelockTime != relock_time) return;
shared_wallet->Lock();
shared_wallet->nRelockTime = 0;
}
Expand Down
4 changes: 3 additions & 1 deletion src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -965,8 +965,10 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
std::vector<std::string> GetDestValues(const std::string& prefix) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);

//! Holds a timestamp at which point the wallet is scheduled (externally) to be relocked. Caller must arrange for actual relocking to occur via Lock().
int64_t nRelockTime = 0;
int64_t nRelockTime GUARDED_BY(cs_wallet){0};

// Used to prevent concurrent calls to walletpassphrase RPC.
Mutex m_unlock_mutex;
bool Unlock(const SecureString& strWalletPassphrase, bool fForMixingOnly = false, bool accept_no_keys = false);
bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase);
bool EncryptWallet(const SecureString& strWalletPassphrase);
Expand Down

0 comments on commit f4ced33

Please sign in to comment.