Skip to content

Commit 99e93de

Browse files
committed
Merge #11476: Avoid opening copied wallet databases simultaneously
478a89c Avoid opening copied wallet databases simultaneously (Russell Yanofsky) Pull request description: Make sure wallet databases have unique fileids. If they don't, throw an error. BDB caches do not work properly when more than one open database has the same fileid, because values written to one database may show up in reads to other databases. Bitcoin will never create different databases with the same fileid, but users can create them by manually copying database files. BDB caching bug was reported by @dooglus in bitcoin/bitcoin#11429 Tree-SHA512: e7635dc81a181801f42324b72fe9e0a2a7dd00b1dcf5abcbf27fa50938eb9a1fc3065c2321326c3456c48c29ae6504353b02f3d46e6eb2f7b09e46d8fe24388d
2 parents 13f53b7 + 478a89c commit 99e93de

File tree

2 files changed

+41
-0
lines changed

2 files changed

+41
-0
lines changed

src/wallet/db.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,40 @@
2020

2121
#include <boost/thread.hpp>
2222

23+
namespace {
24+
//! Make sure database has a unique fileid within the environment. If it
25+
//! doesn't, throw an error. BDB caches do not work properly when more than one
26+
//! open database has the same fileid (values written to one database may show
27+
//! up in reads to other databases).
28+
//!
29+
//! BerkeleyDB generates unique fileids by default
30+
//! (https://docs.oracle.com/cd/E17275_01/html/programmer_reference/program_copy.html),
31+
//! so bitcoin should never create different databases with the same fileid, but
32+
//! this error can be triggered if users manually copy database files.
33+
void CheckUniqueFileid(const CDBEnv& env, const std::string& filename, Db& db)
34+
{
35+
if (env.IsMock()) return;
36+
37+
u_int8_t fileid[DB_FILE_ID_LEN];
38+
int ret = db.get_mpf()->get_fileid(fileid);
39+
if (ret != 0) {
40+
throw std::runtime_error(strprintf("CDB: Can't open database %s (get_fileid failed with %d)", filename, ret));
41+
}
42+
43+
for (const auto& item : env.mapDb) {
44+
u_int8_t item_fileid[DB_FILE_ID_LEN];
45+
if (item.second && item.second->get_mpf()->get_fileid(item_fileid) == 0 &&
46+
memcmp(fileid, item_fileid, sizeof(fileid)) == 0) {
47+
const char* item_filename = nullptr;
48+
item.second->get_dbname(&item_filename, nullptr);
49+
throw std::runtime_error(strprintf("CDB: Can't open database %s (duplicates fileid %s from %s)", filename,
50+
HexStr(std::begin(item_fileid), std::end(item_fileid)),
51+
item_filename ? item_filename : "(unknown database)"));
52+
}
53+
}
54+
}
55+
} // namespace
56+
2357
//
2458
// CDB
2559
//
@@ -403,6 +437,7 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb
403437
if (ret != 0) {
404438
throw std::runtime_error(strprintf("CDB: Error %d, can't open database %s", ret, strFilename));
405439
}
440+
CheckUniqueFileid(*env, strFilename, *pdb_temp);
406441

407442
pdb = pdb_temp.release();
408443
env->mapDb[strFilename] = pdb;

test/functional/multiwallet.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
Verify that a bitcoind node can load multiple wallet files
88
"""
99
import os
10+
import shutil
1011

1112
from test_framework.test_framework import BitcoinTestFramework
1213
from test_framework.util import assert_equal, assert_raises_rpc_error
@@ -29,6 +30,11 @@ def run_test(self):
2930
os.mkdir(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w11'))
3031
self.assert_start_raises_init_error(0, ['-wallet=w11'], 'Error loading wallet w11. -wallet filename must be a regular file.')
3132

33+
# should not initialize if one wallet is a copy of another
34+
shutil.copyfile(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w2'),
35+
os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w22'))
36+
self.assert_start_raises_init_error(0, ['-wallet=w2', '-wallet=w22'], 'duplicates fileid')
37+
3238
# should not initialize if wallet file is a symlink
3339
os.symlink(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w1'), os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w12'))
3440
self.assert_start_raises_init_error(0, ['-wallet=w12'], 'Error loading wallet w12. -wallet filename must be a regular file.')

0 commit comments

Comments
 (0)