Skip to content

Commit 8aefa9b

Browse files
committed
merge bitcoin#22762: Raise InitError when peers.dat is invalid or corrupted
1 parent c9a645f commit 8aefa9b

File tree

6 files changed

+88
-67
lines changed

6 files changed

+88
-67
lines changed

src/addrdb.cpp

Lines changed: 54 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,17 @@
1717
#include <univalue.h>
1818
#include <util/settings.h>
1919
#include <util/system.h>
20+
#include <util/translation.h>
2021

2122
#include <cstdint>
2223

2324
namespace {
25+
26+
class DbNotFoundError : public std::exception
27+
{
28+
using std::exception::exception;
29+
};
30+
2431
template <typename Stream, typename Data>
2532
bool SerializeDB(Stream& stream, const Data& data)
2633
{
@@ -78,47 +85,40 @@ bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data
7885
}
7986

8087
template <typename Stream, typename Data>
81-
bool DeserializeDB(Stream& stream, Data& data, bool fCheckSum = true)
88+
void DeserializeDB(Stream& stream, Data& data, bool fCheckSum = true)
8289
{
83-
try {
84-
CHashVerifier<Stream> verifier(&stream);
85-
// de-serialize file header (network specific magic number) and ..
86-
unsigned char pchMsgTmp[4];
87-
verifier >> pchMsgTmp;
88-
// ... verify the network matches ours
89-
if (memcmp(pchMsgTmp, Params().MessageStart(), sizeof(pchMsgTmp)))
90-
return error("%s: Invalid network magic number", __func__);
91-
92-
// de-serialize data
93-
verifier >> data;
94-
95-
// verify checksum
96-
if (fCheckSum) {
97-
uint256 hashTmp;
98-
stream >> hashTmp;
99-
if (hashTmp != verifier.GetHash()) {
100-
return error("%s: Checksum mismatch, data corrupted", __func__);
101-
}
102-
}
103-
}
104-
catch (const std::exception& e) {
105-
return error("%s: Deserialize or I/O error - %s", __func__, e.what());
90+
CHashVerifier<Stream> verifier(&stream);
91+
// de-serialize file header (network specific magic number) and ..
92+
unsigned char pchMsgTmp[4];
93+
verifier >> pchMsgTmp;
94+
// ... verify the network matches ours
95+
if (memcmp(pchMsgTmp, Params().MessageStart(), sizeof(pchMsgTmp))) {
96+
throw std::runtime_error{"Invalid network magic number"};
10697
}
10798

108-
return true;
99+
// de-serialize data
100+
verifier >> data;
101+
102+
// verify checksum
103+
if (fCheckSum) {
104+
uint256 hashTmp;
105+
stream >> hashTmp;
106+
if (hashTmp != verifier.GetHash()) {
107+
throw std::runtime_error{"Checksum mismatch, data corrupted"};
108+
}
109+
}
109110
}
110111

111112
template <typename Data>
112-
bool DeserializeFileDB(const fs::path& path, Data& data, int version)
113+
void DeserializeFileDB(const fs::path& path, Data& data, int version)
113114
{
114115
// open input file, and associate with CAutoFile
115116
FILE* file = fsbridge::fopen(path, "rb");
116117
CAutoFile filein(file, SER_DISK, version);
117118
if (filein.IsNull()) {
118-
LogPrintf("Missing or invalid file %s\n", path.string());
119-
return false;
119+
throw DbNotFoundError{};
120120
}
121-
return DeserializeDB(filein, data);
121+
DeserializeDB(filein, data);
122122
}
123123
} // namespace
124124

@@ -177,15 +177,32 @@ bool DumpPeerAddresses(const ArgsManager& args, const CAddrMan& addr)
177177
return SerializeFileDB("peers", pathAddr, addr, CLIENT_VERSION);
178178
}
179179

180-
bool ReadPeerAddresses(const ArgsManager& args, CAddrMan& addr)
180+
void ReadFromStream(CAddrMan& addr, CDataStream& ssPeers)
181181
{
182-
const auto pathAddr = GetDataDir() / "peers.dat";
183-
return DeserializeFileDB(pathAddr, addr, CLIENT_VERSION);
182+
DeserializeDB(ssPeers, addr, false);
184183
}
185184

186-
bool ReadFromStream(CAddrMan& addr, CDataStream& ssPeers)
185+
std::optional<bilingual_str> LoadAddrman(const std::vector<bool>& asmap, const ArgsManager& args, std::unique_ptr<CAddrMan>& addrman)
187186
{
188-
return DeserializeDB(ssPeers, addr, false);
187+
auto check_addrman = std::clamp<int32_t>(args.GetArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000);
188+
addrman = std::make_unique<CAddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman);
189+
190+
int64_t nStart = GetTimeMillis();
191+
const auto path_addr{GetDataDir() / "peers.dat"};
192+
try {
193+
DeserializeFileDB(path_addr, *addrman, CLIENT_VERSION);
194+
LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman->size(), GetTimeMillis() - nStart);
195+
} catch (const DbNotFoundError&) {
196+
// Addrman can be in an inconsistent state after failure, reset it
197+
addrman = std::make_unique<CAddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman);
198+
LogPrintf("Creating peers.dat because the file was not found (%s)\n", path_addr);
199+
DumpPeerAddresses(args, *addrman);
200+
} catch (const std::exception& e) {
201+
addrman = nullptr;
202+
return strprintf(_("Invalid or corrupt peers.dat (%s). If you believe this is a bug, please report it to %s. As a workaround, you can move the file (%s) out of the way (rename, move, or delete) to have a new one created on the next start."),
203+
e.what(), PACKAGE_BUGREPORT, path_addr);
204+
}
205+
return std::nullopt;
189206
}
190207

191208
void DumpAnchors(const fs::path& anchors_db_path, const std::vector<CAddress>& anchors)
@@ -197,9 +214,10 @@ void DumpAnchors(const fs::path& anchors_db_path, const std::vector<CAddress>& a
197214
std::vector<CAddress> ReadAnchors(const fs::path& anchors_db_path)
198215
{
199216
std::vector<CAddress> anchors;
200-
if (DeserializeFileDB(anchors_db_path, anchors, CLIENT_VERSION | ADDRV2_FORMAT)) {
217+
try {
218+
DeserializeFileDB(anchors_db_path, anchors, CLIENT_VERSION | ADDRV2_FORMAT);
201219
LogPrintf("Loaded %i addresses from %s\n", anchors.size(), anchors_db_path.filename());
202-
} else {
220+
} catch (const std::exception&) {
203221
anchors.clear();
204222
}
205223

src/addrdb.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,18 @@
1010
#include <net_types.h> // For banmap_t
1111
#include <univalue.h>
1212

13+
#include <optional>
1314
#include <vector>
1415

1516
class ArgsManager;
1617
class CAddrMan;
1718
class CAddress;
1819
class CDataStream;
20+
struct bilingual_str;
1921

2022
bool DumpPeerAddresses(const ArgsManager& args, const CAddrMan& addr);
21-
bool ReadPeerAddresses(const ArgsManager& args, CAddrMan& addr);
2223
/** Only used by tests. */
23-
bool ReadFromStream(CAddrMan& addr, CDataStream& ssPeers);
24+
void ReadFromStream(CAddrMan& addr, CDataStream& ssPeers);
2425

2526
/** Access to the banlist database (banlist.json) */
2627
class CBanDB
@@ -46,6 +47,9 @@ class CBanDB
4647
bool Read(banmap_t& banSet);
4748
};
4849

50+
/** Returns an error string on failure */
51+
std::optional<bilingual_str> LoadAddrman(const std::vector<bool>& asmap, const ArgsManager& args, std::unique_ptr<CAddrMan>& addrman);
52+
4953
/**
5054
* Dump the anchor IP address database (anchors.dat)
5155
*

src/init.cpp

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,19 +1693,9 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
16931693
LogPrintf("Using /16 prefix for IP bucketing\n");
16941694
}
16951695

1696-
auto check_addrman = std::clamp<int32_t>(args.GetArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000);
1697-
node.addrman = std::make_unique<CAddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman);
1698-
1699-
// Load addresses from peers.dat
17001696
uiInterface.InitMessage(_("Loading P2P addresses…").translated);
1701-
int64_t nStart = GetTimeMillis();
1702-
if (ReadPeerAddresses(args, *node.addrman)) {
1703-
LogPrintf("Loaded %i addresses from peers.dat %dms\n", node.addrman->size(), GetTimeMillis() - nStart);
1704-
} else {
1705-
// Addrman can be in an inconsistent state after failure, reset it
1706-
node.addrman = std::make_unique<CAddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman);
1707-
LogPrintf("Recreating peers.dat\n");
1708-
DumpPeerAddresses(args, *node.addrman);
1697+
if (const auto error{LoadAddrman(asmap, args, node.addrman)}) {
1698+
return InitError(*error);
17091699
}
17101700
}
17111701

src/test/addrman_tests.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,7 +1042,7 @@ BOOST_AUTO_TEST_CASE(load_addrman)
10421042

10431043
CAddrMan addrman2(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 100);
10441044
BOOST_CHECK(addrman2.size() == 0);
1045-
BOOST_CHECK(ReadFromStream(addrman2, ssPeers2));
1045+
ReadFromStream(addrman2, ssPeers2);
10461046
BOOST_CHECK(addrman2.size() == 3);
10471047
}
10481048

@@ -1072,7 +1072,7 @@ BOOST_AUTO_TEST_CASE(load_addrman_corrupted)
10721072

10731073
CAddrMan addrman2(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 100);
10741074
BOOST_CHECK(addrman2.size() == 0);
1075-
BOOST_CHECK(!ReadFromStream(addrman2, ssPeers2));
1075+
BOOST_CHECK_THROW(ReadFromStream(addrman2, ssPeers2), std::ios_base::failure);
10761076
}
10771077

10781078
BOOST_AUTO_TEST_SUITE_END()

src/test/fuzz/data_stream.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@ FUZZ_TARGET_INIT(data_stream_addr_man, initialize_data_stream_addr_man)
2323
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
2424
CDataStream data_stream = ConsumeDataStream(fuzzed_data_provider);
2525
CAddrMan addr_man(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 0);
26-
ReadFromStream(addr_man, data_stream);
26+
try {
27+
ReadFromStream(addr_man, data_stream);
28+
} catch (const std::exception&) {
29+
}
2730
}

test/functional/feature_addrman.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from test_framework.messages import ser_uint256, hash256
1111
from test_framework.p2p import MAGIC_BYTES
1212
from test_framework.test_framework import BitcoinTestFramework
13+
from test_framework.test_node import ErrorMatch
1314
from test_framework.util import assert_equal
1415

1516

@@ -43,6 +44,12 @@ def set_test_params(self):
4344

4445
def run_test(self):
4546
peers_dat = os.path.join(self.nodes[0].datadir, self.chain, "peers.dat")
47+
init_error = lambda reason: (
48+
f"Error: Invalid or corrupt peers.dat \\({reason}\\). If you believe this "
49+
f"is a bug, please report it to {self.config['environment']['PACKAGE_BUGREPORT']}. "
50+
f'As a workaround, you can move the file \\("{peers_dat}"\\) out of the way \\(rename, '
51+
"move, or delete\\) to have a new one created on the next start."
52+
)
4653

4754
self.log.info("Check that mocked addrman is valid")
4855
self.stop_node(0)
@@ -54,30 +61,29 @@ def run_test(self):
5461
self.log.info("Check that addrman from future cannot be read")
5562
self.stop_node(0)
5663
write_addrman(peers_dat, lowest_compatible=111)
57-
with self.nodes[0].assert_debug_log([
58-
f'ERROR: DeserializeDB: Deserialize or I/O error - Unsupported format of addrman database: 1. It is compatible with formats >=111, but the maximum supported by this version of {self.config["environment"]["PACKAGE_NAME"]} is 3.',
59-
"Recreating peers.dat",
60-
]):
61-
self.start_node(0)
62-
assert_equal(self.nodes[0].getnodeaddresses(), [])
64+
self.nodes[0].assert_start_raises_init_error(
65+
expected_msg=init_error(
66+
"Unsupported format of addrman database: 1. It is compatible with "
67+
"formats >=111, but the maximum supported by this version of "
68+
f"{self.config['environment']['PACKAGE_NAME']} is 3.: (.+)"
69+
),
70+
match=ErrorMatch.FULL_REGEX,
71+
)
6372

6473
self.log.info("Check that corrupt addrman cannot be read")
6574
self.stop_node(0)
6675
with open(peers_dat, "wb") as f:
6776
f.write(serialize_addrman()[:-1])
68-
with self.nodes[0].assert_debug_log([
69-
"ERROR: DeserializeDB: Deserialize or I/O error - CAutoFile::read: end of file",
70-
"Recreating peers.dat",
71-
]):
72-
self.start_node(0)
73-
assert_equal(self.nodes[0].getnodeaddresses(), [])
77+
self.nodes[0].assert_start_raises_init_error(
78+
expected_msg=init_error("CAutoFile::read: end of file.*"),
79+
match=ErrorMatch.FULL_REGEX,
80+
)
7481

7582
self.log.info("Check that missing addrman is recreated")
7683
self.stop_node(0)
7784
os.remove(peers_dat)
7885
with self.nodes[0].assert_debug_log([
79-
f"Missing or invalid file {peers_dat}",
80-
"Recreating peers.dat",
86+
f'Creating peers.dat because the file was not found ("{peers_dat}")',
8187
]):
8288
self.start_node(0)
8389
assert_equal(self.nodes[0].getnodeaddresses(), [])

0 commit comments

Comments
 (0)