Skip to content

Commit

Permalink
Merge #12493: [wallet] Reopen CDBEnv after encryption instead of shut…
Browse files Browse the repository at this point in the history
…ting down

c1dde3a No longer shutdown after encrypting the wallet (Andrew Chow)
d7637c5 After encrypting the wallet, reload the database environment (Andrew Chow)
5d296ac Add function to close all Db's and reload the databae environment (Andrew Chow)
a769461 Move BerkeleyEnvironment deletion from internal method to callsite (Andrew Chow)

Pull request description:

  This is the replacement for #11678 which implements @ryanofsky's [suggestion](bitcoin/bitcoin#11678 (review)).

  Shutting down the software was to prevent the BDB environment from writing unencrypted private keys to disk in the database log files, as was noted [here](https://bitcointalk.org/index.php?topic=51474.msg616068#msg616068). This PR replaces the shutdown behavior with a CDBEnv flush, close, and reopen which achieves the same effect: everything is cleanly flushed and closed, the log files are removed, and then the environment reopened to continue normal operation.

  To ensure that no unencrypted private keys are in the log files after encrypting the wallet, I wrote [this script](https://gist.github.com/achow101/7f7143e6c3d3fdc034d3470e72823e9d) to pull private keys from the original wallet file and searches for these keys in the log files (note that you will have to change your file paths to make it work on your own machine).

  As for concerns about private keys being written to slack space or being kept in memory, these behaviors no longer exist after the original wallet encryption PR and the shutting down solution from 2011.

  cc @ryanofsky

Tree-SHA512: 34b894283b0677a873d06dee46dff8424dec85a2973009ac9b84bcf3d22d05f227c494168c395219d9aee3178e420cf70d4b3eeacc9785aa86b6015d25758e75
  • Loading branch information
laanwj committed Sep 14, 2018
2 parents a098245 + c1dde3a commit f09bc7e
Show file tree
Hide file tree
Showing 11 changed files with 57 additions and 32 deletions.
5 changes: 2 additions & 3 deletions src/qt/askpassphrasedialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,15 @@ void AskPassphraseDialog::accept()
{
QMessageBox::warning(this, tr("Wallet encrypted"),
"<qt>" +
tr("%1 will close now to finish the encryption process. "
tr("Your wallet is now encrypted. "
"Remember that encrypting your wallet cannot fully protect "
"your bitcoins from being stolen by malware infecting your computer.").arg(tr(PACKAGE_NAME)) +
"your bitcoins from being stolen by malware infecting your computer.") +
"<br><br><b>" +
tr("IMPORTANT: Any previous backups you have made of your wallet file "
"should be replaced with the newly generated, encrypted wallet file. "
"For security reasons, previous backups of the unencrypted wallet file "
"will become useless as soon as you start using the new, encrypted wallet.") +
"</b></qt>");
QApplication::quit();
}
else
{
Expand Down
41 changes: 39 additions & 2 deletions src/wallet/db.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ void BerkeleyBatch::Close()
LOCK(cs_db);
--env->mapFileUseCount[strFile];
}
env->m_db_in_use.notify_all();
}

void BerkeleyEnvironment::CloseDb(const std::string& strFile)
Expand All @@ -572,6 +573,32 @@ void BerkeleyEnvironment::CloseDb(const std::string& strFile)
}
}

void BerkeleyEnvironment::ReloadDbEnv()
{
// Make sure that no Db's are in use
AssertLockNotHeld(cs_db);
std::unique_lock<CCriticalSection> lock(cs_db);
m_db_in_use.wait(lock, [this](){
for (auto& count : mapFileUseCount) {
if (count.second > 0) return false;
}
return true;
});

std::vector<std::string> filenames;
for (auto it : mapDb) {
filenames.push_back(it.first);
}
// Close the individual Db's
for (const std::string& filename : filenames) {
CloseDb(filename);
}
// Reset the environment
Flush(true); // This will flush and close the environment
Reset();
Open(true);
}

bool BerkeleyBatch::Rewrite(BerkeleyDatabase& database, const char* pszSkip)
{
if (database.IsDummy()) {
Expand Down Expand Up @@ -697,7 +724,6 @@ void BerkeleyEnvironment::Flush(bool fShutdown)
if (!fMockDb) {
fs::remove_all(fs::path(strPath) / "database");
}
g_dbenvs.erase(strPath);
}
}
}
Expand Down Expand Up @@ -796,6 +822,17 @@ void BerkeleyDatabase::Flush(bool shutdown)
{
if (!IsDummy()) {
env->Flush(shutdown);
if (shutdown) env = nullptr;
if (shutdown) {
LOCK(cs_db);
g_dbenvs.erase(env->Directory().string());
env = nullptr;
}
}
}

void BerkeleyDatabase::ReloadDbEnv()
{
if (!IsDummy()) {
env->ReloadDbEnv();
}
}
4 changes: 4 additions & 0 deletions src/wallet/db.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class BerkeleyEnvironment
std::unique_ptr<DbEnv> dbenv;
std::map<std::string, int> mapFileUseCount;
std::map<std::string, Db*> mapDb;
std::condition_variable_any m_db_in_use;

BerkeleyEnvironment(const fs::path& env_directory);
~BerkeleyEnvironment();
Expand Down Expand Up @@ -75,6 +76,7 @@ class BerkeleyEnvironment
void CheckpointLSN(const std::string& strFile);

void CloseDb(const std::string& strFile);
void ReloadDbEnv();

DbTxn* TxnBegin(int flags = DB_TXN_WRITE_NOSYNC)
{
Expand Down Expand Up @@ -145,6 +147,8 @@ class BerkeleyDatabase

void IncrementUpdateCounter();

void ReloadDbEnv();

std::atomic<unsigned int> nUpdateCounter;
unsigned int nLastSeen;
unsigned int nLastFlushed;
Expand Down
7 changes: 1 addition & 6 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2121,7 +2121,6 @@ static UniValue encryptwallet(const JSONRPCRequest& request)
"will require the passphrase to be set prior the making these calls.\n"
"Use the walletpassphrase call for this, and then walletlock call.\n"
"If the wallet is already encrypted, use the walletpassphrasechange call.\n"
"Note that this will shutdown the server.\n"
"\nArguments:\n"
"1. \"passphrase\" (string) The pass phrase to encrypt the wallet with. It must be at least 1 character, but should be long.\n"
"\nExamples:\n"
Expand Down Expand Up @@ -2159,11 +2158,7 @@ static UniValue encryptwallet(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet.");
}

// BDB seems to have a bad habit of writing old data into
// slack space in .dat files; that is bad if the old data is
// unencrypted private keys. So:
StartShutdown();
return "wallet encrypted; Bitcoin server stopping, restart to run with encrypted wallet. The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.";
return "wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.";
}

static UniValue lockunspent(const JSONRPCRequest& request)
Expand Down
5 changes: 5 additions & 0 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,11 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
// bits of the unencrypted private key in slack space in the database file.
database->Rewrite();

// BDB seems to have a bad habit of writing old data into
// slack space in .dat files; that is bad if the old data is
// unencrypted private keys. So:
database->ReloadDbEnv();

}
NotifyStatusChanged(this);

Expand Down
6 changes: 2 additions & 4 deletions test/functional/rpc_fundrawtransaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,10 +478,8 @@ def run_test(self):

############################################################
# locked wallet test
self.stop_node(0)
self.nodes[1].node_encrypt_wallet("test")
self.stop_node(2)
self.stop_node(3)
self.nodes[1].encryptwallet("test")
self.stop_nodes()

self.start_nodes()
# This test is not meant to test fee estimation and we'd like
Expand Down
8 changes: 0 additions & 8 deletions test/functional/test_framework/test_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,14 +305,6 @@ def assert_start_raises_init_error(self, extra_args=None, expected_msg=None, mat
assert_msg = "bitcoind should have exited with expected error " + expected_msg
self._raise_assertion_error(assert_msg)

def node_encrypt_wallet(self, passphrase):
""""Encrypts the wallet.
This causes bitcoind to shutdown, so this method takes
care of cleaning up resources."""
self.encryptwallet(passphrase)
self.wait_until_stopped()

def add_p2p_connection(self, p2p_conn, *, wait_for_verack=True, **kwargs):
"""Add a p2p connection to the node.
Expand Down
3 changes: 1 addition & 2 deletions test/functional/wallet_bumpfee.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ def skip_test_if_missing_module(self):

def run_test(self):
# Encrypt wallet for test_locked_wallet_fails test
self.nodes[1].node_encrypt_wallet(WALLET_PASSPHRASE)
self.start_node(1)
self.nodes[1].encryptwallet(WALLET_PASSPHRASE)
self.nodes[1].walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)

connect_nodes_bi(self.nodes, 0, 1)
Expand Down
3 changes: 1 addition & 2 deletions test/functional/wallet_dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,7 @@ def run_test(self):
assert_equal(witness_addr_ret, witness_addr) # p2sh-p2wsh address added to the first key

#encrypt wallet, restart, unlock and dump
self.nodes[0].node_encrypt_wallet('test')
self.start_node(0)
self.nodes[0].encryptwallet('test')
self.nodes[0].walletpassphrase('test', 10)
# Should be a no-op:
self.nodes[0].keypoolrefill()
Expand Down
3 changes: 1 addition & 2 deletions test/functional/wallet_encryption.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ def run_test(self):
assert_equal(len(privkey), 52)

# Encrypt the wallet
self.nodes[0].node_encrypt_wallet(passphrase)
self.start_node(0)
self.nodes[0].encryptwallet(passphrase)

# Test that the wallet is encrypted
assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].dumpprivkey, address)
Expand Down
4 changes: 1 addition & 3 deletions test/functional/wallet_keypool.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ def run_test(self):
assert(addr_before_encrypting_data['hdseedid'] == wallet_info_old['hdseedid'])

# Encrypt wallet and wait to terminate
nodes[0].node_encrypt_wallet('test')
# Restart node 0
self.start_node(0)
nodes[0].encryptwallet('test')
# Keep creating keys
addr = nodes[0].getnewaddress()
addr_data = nodes[0].getaddressinfo(addr)
Expand Down

0 comments on commit f09bc7e

Please sign in to comment.