Skip to content

Commit 4d7a3ae

Browse files
TheCharlatanachow101
authored andcommitted
Berkeley RO Database fuzz test
1 parent 3568dce commit 4d7a3ae

File tree

2 files changed

+135
-1
lines changed

2 files changed

+135
-1
lines changed

src/Makefile.test.include

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ FUZZ_WALLET_SRC = \
203203
wallet/test/fuzz/coincontrol.cpp \
204204
wallet/test/fuzz/coinselection.cpp \
205205
wallet/test/fuzz/fees.cpp \
206-
wallet/test/fuzz/parse_iso8601.cpp
206+
wallet/test/fuzz/parse_iso8601.cpp \
207+
wallet/test/fuzz/wallet_bdb_parser.cpp
207208

208209
if USE_SQLITE
209210
FUZZ_WALLET_SRC += \
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright (c) 2023 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <config/bitcoin-config.h> // IWYU pragma: keep
6+
#include <test/fuzz/FuzzedDataProvider.h>
7+
#include <test/fuzz/fuzz.h>
8+
#include <test/fuzz/util.h>
9+
#include <test/util/setup_common.h>
10+
#include <util/fs.h>
11+
#include <util/time.h>
12+
#include <util/translation.h>
13+
#include <wallet/bdb.h>
14+
#include <wallet/db.h>
15+
#include <wallet/dump.h>
16+
#include <wallet/migrate.h>
17+
18+
#include <fstream>
19+
#include <iostream>
20+
21+
using wallet::DatabaseOptions;
22+
using wallet::DatabaseStatus;
23+
24+
namespace {
25+
TestingSetup* g_setup;
26+
} // namespace
27+
28+
void initialize_wallet_bdb_parser()
29+
{
30+
static auto testing_setup = MakeNoLogFileContext<TestingSetup>();
31+
g_setup = testing_setup.get();
32+
}
33+
34+
FUZZ_TARGET(wallet_bdb_parser, .init = initialize_wallet_bdb_parser)
35+
{
36+
const auto wallet_path = g_setup->m_args.GetDataDirNet() / "fuzzed_wallet.dat";
37+
38+
{
39+
AutoFile outfile{fsbridge::fopen(wallet_path, "wb")};
40+
outfile << Span{buffer};
41+
}
42+
43+
const DatabaseOptions options{};
44+
DatabaseStatus status;
45+
bilingual_str error;
46+
47+
fs::path bdb_ro_dumpfile{g_setup->m_args.GetDataDirNet() / "fuzzed_dumpfile_bdb_ro.dump"};
48+
if (fs::exists(bdb_ro_dumpfile)) { // Writing into an existing dump file will throw an exception
49+
remove(bdb_ro_dumpfile);
50+
}
51+
g_setup->m_args.ForceSetArg("-dumpfile", fs::PathToString(bdb_ro_dumpfile));
52+
53+
#ifdef USE_BDB
54+
bool bdb_ro_err = false;
55+
bool bdb_ro_pgno_err = false;
56+
#endif
57+
auto db{MakeBerkeleyRODatabase(wallet_path, options, status, error)};
58+
if (db) {
59+
assert(DumpWallet(g_setup->m_args, *db, error));
60+
} else {
61+
#ifdef USE_BDB
62+
bdb_ro_err = true;
63+
#endif
64+
if (error.original == "AutoFile::ignore: end of file: iostream error" ||
65+
error.original == "AutoFile::read: end of file: iostream error" ||
66+
error.original == "Not a BDB file" ||
67+
error.original == "Unsupported BDB data file version number" ||
68+
error.original == "Unexpected page type, should be 9 (BTree Metadata)" ||
69+
error.original == "Unexpected database flags, should only be 0x20 (subdatabases)" ||
70+
error.original == "Unexpected outer database root page type" ||
71+
error.original == "Unexpected number of entries in outer database root page" ||
72+
error.original == "Subdatabase has an unexpected name" ||
73+
error.original == "Subdatabase page number has unexpected length" ||
74+
error.original == "Unexpected inner database page type" ||
75+
error.original == "Unknown record type in records page" ||
76+
error.original == "Unknown record type in internal page" ||
77+
error.original == "Unexpected page size" ||
78+
error.original == "Unexpected page type" ||
79+
error.original == "Page number mismatch" ||
80+
error.original == "Bad btree level" ||
81+
error.original == "Bad page size" ||
82+
error.original == "File size is not a multiple of page size" ||
83+
error.original == "Meta page number mismatch") {
84+
// Do nothing
85+
} else if (error.original == "Subdatabase last page is greater than database last page" ||
86+
error.original == "Page number is greater than database last page" ||
87+
error.original == "Page number is greater than subdatabase last page" ||
88+
error.original == "Last page number could not fit in file") {
89+
#ifdef USE_BDB
90+
bdb_ro_pgno_err = true;
91+
#endif
92+
} else {
93+
throw std::runtime_error(error.original);
94+
}
95+
}
96+
97+
#ifdef USE_BDB
98+
// Try opening with BDB
99+
fs::path bdb_dumpfile{g_setup->m_args.GetDataDirNet() / "fuzzed_dumpfile_bdb.dump"};
100+
if (fs::exists(bdb_dumpfile)) { // Writing into an existing dump file will throw an exception
101+
remove(bdb_dumpfile);
102+
}
103+
g_setup->m_args.ForceSetArg("-dumpfile", fs::PathToString(bdb_dumpfile));
104+
105+
try {
106+
auto db{MakeBerkeleyDatabase(wallet_path, options, status, error)};
107+
if (bdb_ro_err && !db) {
108+
return;
109+
}
110+
assert(db);
111+
if (bdb_ro_pgno_err) {
112+
// BerkeleyRO will throw on opening for errors involving bad page numbers, but BDB does not.
113+
// Ignore those.
114+
return;
115+
}
116+
assert(!bdb_ro_err);
117+
assert(DumpWallet(g_setup->m_args, *db, error));
118+
} catch (const std::runtime_error& e) {
119+
if (bdb_ro_err) return;
120+
throw e;
121+
}
122+
123+
// Make sure the dumpfiles match
124+
if (fs::exists(bdb_ro_dumpfile) && fs::exists(bdb_dumpfile)) {
125+
std::ifstream bdb_ro_dump(bdb_ro_dumpfile, std::ios_base::binary | std::ios_base::in);
126+
std::ifstream bdb_dump(bdb_dumpfile, std::ios_base::binary | std::ios_base::in);
127+
assert(std::equal(
128+
std::istreambuf_iterator<char>(bdb_ro_dump.rdbuf()),
129+
std::istreambuf_iterator<char>(),
130+
std::istreambuf_iterator<char>(bdb_dump.rdbuf())));
131+
}
132+
#endif
133+
}

0 commit comments

Comments
 (0)