Skip to content

Commit afa506f

Browse files
committed
Merge #14552: wallet: detecting duplicate wallet by comparing the db filename.
5912031 wallet: Create IsDatabaseLoaded function (Chun Kuan Lee) 15c93f0 wallet: Add trailing wallet.dat when detecting duplicate wallet if it's a directory. (Chun Kuan Lee) c456fbd Refactor: Move m_db pointers into BerkeleyDatabase (Russell Yanofsky) Pull request description: Fix #14538 Fix crash attempting to load the same wallet with different path strings that resolve to the same absolute path. The primary check which prevents loading the same wallet twice is: https://github.com/bitcoin/bitcoin/blob/6b8d0a2164b30eab76e7bccb1ffb056a10fba406/src/wallet/db.cpp#L44 But this check is skipped if both wallet paths resolve to the same absolute path, due to caching here: https://github.com/bitcoin/bitcoin/blob/6b8d0a2164b30eab76e7bccb1ffb056a10fba406/src/wallet/db.cpp#L467 Meanwhile a secondary check for duplicate wallets is not reliable because it based on a literal comparison, instead of comparison using absolute paths: https://github.com/bitcoin/bitcoin/blob/6b8d0a2164b30eab76e7bccb1ffb056a10fba406/src/wallet/wallet.cpp#L3853 This PR fixes the latter check to compare the absolute path of a new wallet being loaded to absolute paths of wallets already loaded, so there should no longer be any way to load the same wallet more than once. Tree-SHA512: 2fa01811c160b57be3b76c6b4983556a04bbce71a3f8202429987ec020664a062e897deedcd9248bc04e9baaa2fc7b464e2595dcaeff2af0818387bf1fcdbf6f
2 parents 1b99d15 + 5912031 commit afa506f

File tree

4 files changed

+57
-21
lines changed

4 files changed

+57
-21
lines changed

src/wallet/db.cpp

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,8 @@ bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const
5656
return memcmp(value, &rhs.value, sizeof(value)) == 0;
5757
}
5858

59-
BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename)
59+
static void SplitWalletPath(const fs::path& wallet_path, fs::path& env_directory, std::string& database_filename)
6060
{
61-
fs::path env_directory;
6261
if (fs::is_regular_file(wallet_path)) {
6362
// Special case for backwards compatibility: if wallet path points to an
6463
// existing file, treat it as the path to a BDB data file in a parent
@@ -71,6 +70,23 @@ BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& data
7170
env_directory = wallet_path;
7271
database_filename = "wallet.dat";
7372
}
73+
}
74+
75+
bool IsWalletLoaded(const fs::path& wallet_path)
76+
{
77+
fs::path env_directory;
78+
std::string database_filename;
79+
SplitWalletPath(wallet_path, env_directory, database_filename);
80+
LOCK(cs_db);
81+
auto env = g_dbenvs.find(env_directory.string());
82+
if (env == g_dbenvs.end()) return false;
83+
return env->second.IsDatabaseLoaded(database_filename);
84+
}
85+
86+
BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename)
87+
{
88+
fs::path env_directory;
89+
SplitWalletPath(wallet_path, env_directory, database_filename);
7490
LOCK(cs_db);
7591
// Note: An unused temporary BerkeleyEnvironment object may be created inside the
7692
// emplace function if the key already exists. This is a little inefficient,
@@ -90,13 +106,13 @@ void BerkeleyEnvironment::Close()
90106

91107
fDbEnvInit = false;
92108

93-
for (auto& db : mapDb) {
109+
for (auto& db : m_databases) {
94110
auto count = mapFileUseCount.find(db.first);
95111
assert(count == mapFileUseCount.end() || count->second == 0);
96-
if (db.second) {
97-
db.second->close(0);
98-
delete db.second;
99-
db.second = nullptr;
112+
BerkeleyDatabase& database = db.second.get();
113+
if (database.m_db) {
114+
database.m_db->close(0);
115+
database.m_db.reset();
100116
}
101117
}
102118

@@ -463,7 +479,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
463479
if (!env->Open(false /* retry */))
464480
throw std::runtime_error("BerkeleyBatch: Failed to open database environment.");
465481

466-
pdb = env->mapDb[strFilename];
482+
pdb = database.m_db.get();
467483
if (pdb == nullptr) {
468484
int ret;
469485
std::unique_ptr<Db> pdb_temp = MakeUnique<Db>(env->dbenv.get(), 0);
@@ -508,7 +524,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
508524
}
509525

510526
pdb = pdb_temp.release();
511-
env->mapDb[strFilename] = pdb;
527+
database.m_db.reset(pdb);
512528

513529
if (fCreate && !Exists(std::string("version"))) {
514530
bool fTmp = fReadOnly;
@@ -563,12 +579,13 @@ void BerkeleyEnvironment::CloseDb(const std::string& strFile)
563579
{
564580
{
565581
LOCK(cs_db);
566-
if (mapDb[strFile] != nullptr) {
582+
auto it = m_databases.find(strFile);
583+
assert(it != m_databases.end());
584+
BerkeleyDatabase& database = it->second.get();
585+
if (database.m_db) {
567586
// Close the database handle
568-
Db* pdb = mapDb[strFile];
569-
pdb->close(0);
570-
delete pdb;
571-
mapDb[strFile] = nullptr;
587+
database.m_db->close(0);
588+
database.m_db.reset();
572589
}
573590
}
574591
}
@@ -586,7 +603,7 @@ void BerkeleyEnvironment::ReloadDbEnv()
586603
});
587604

588605
std::vector<std::string> filenames;
589-
for (auto it : mapDb) {
606+
for (auto it : m_databases) {
590607
filenames.push_back(it.first);
591608
}
592609
// Close the individual Db's

src/wallet/db.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ struct WalletDatabaseFileId {
3131
bool operator==(const WalletDatabaseFileId& rhs) const;
3232
};
3333

34+
class BerkeleyDatabase;
35+
3436
class BerkeleyEnvironment
3537
{
3638
private:
@@ -43,7 +45,7 @@ class BerkeleyEnvironment
4345
public:
4446
std::unique_ptr<DbEnv> dbenv;
4547
std::map<std::string, int> mapFileUseCount;
46-
std::map<std::string, Db*> mapDb;
48+
std::map<std::string, std::reference_wrapper<BerkeleyDatabase>> m_databases;
4749
std::unordered_map<std::string, WalletDatabaseFileId> m_fileids;
4850
std::condition_variable_any m_db_in_use;
4951

@@ -54,6 +56,7 @@ class BerkeleyEnvironment
5456
void MakeMock();
5557
bool IsMock() const { return fMockDb; }
5658
bool IsInitialized() const { return fDbEnvInit; }
59+
bool IsDatabaseLoaded(const std::string& db_filename) const { return m_databases.find(db_filename) != m_databases.end(); }
5760
fs::path Directory() const { return strPath; }
5861

5962
/**
@@ -95,6 +98,9 @@ class BerkeleyEnvironment
9598
}
9699
};
97100

101+
/** Return whether a wallet database is currently loaded. */
102+
bool IsWalletLoaded(const fs::path& wallet_path);
103+
98104
/** Get BerkeleyEnvironment and database filename given a wallet path. */
99105
BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename);
100106

@@ -115,13 +121,22 @@ class BerkeleyDatabase
115121
nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0)
116122
{
117123
env = GetWalletEnv(wallet_path, strFile);
124+
auto inserted = env->m_databases.emplace(strFile, std::ref(*this));
125+
assert(inserted.second);
118126
if (mock) {
119127
env->Close();
120128
env->Reset();
121129
env->MakeMock();
122130
}
123131
}
124132

133+
~BerkeleyDatabase() {
134+
if (env) {
135+
size_t erased = env->m_databases.erase(strFile);
136+
assert(erased == 1);
137+
}
138+
}
139+
125140
/** Return object for accessing database at specified path. */
126141
static std::unique_ptr<BerkeleyDatabase> Create(const fs::path& path)
127142
{
@@ -161,6 +176,9 @@ class BerkeleyDatabase
161176
unsigned int nLastFlushed;
162177
int64_t nLastWalletUpdate;
163178

179+
/** Database pointer. This is initialized lazily and reset during flushes, so it can be null. */
180+
std::unique_ptr<Db> m_db;
181+
164182
private:
165183
/** BerkeleyDB specific */
166184
BerkeleyEnvironment *env;

src/wallet/wallet.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3872,11 +3872,9 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b
38723872
}
38733873

38743874
// Make sure that the wallet path doesn't clash with an existing wallet path
3875-
for (auto wallet : GetWallets()) {
3876-
if (wallet->GetLocation().GetPath() == wallet_path) {
3877-
error_string = strprintf("Error loading wallet %s. Duplicate -wallet filename specified.", location.GetName());
3878-
return false;
3879-
}
3875+
if (IsWalletLoaded(wallet_path)) {
3876+
error_string = strprintf("Error loading wallet %s. Duplicate -wallet filename specified.", location.GetName());
3877+
return false;
38803878
}
38813879

38823880
try {

test/functional/wallet_multiwallet.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ def wallet_file(name):
223223
# Fail to load duplicate wallets
224224
assert_raises_rpc_error(-4, 'Wallet file verification failed: Error loading wallet w1. Duplicate -wallet filename specified.', self.nodes[0].loadwallet, wallet_names[0])
225225

226+
# Fail to load duplicate wallets by different ways (directory and filepath)
227+
assert_raises_rpc_error(-4, "Wallet file verification failed: Error loading wallet wallet.dat. Duplicate -wallet filename specified.", self.nodes[0].loadwallet, 'wallet.dat')
228+
226229
# Fail to load if one wallet is a copy of another
227230
assert_raises_rpc_error(-1, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
228231

0 commit comments

Comments
 (0)