Bitcoin Core  29.1.0
P2P Digital Currency
wallet_bdb_parser.cpp
Go to the documentation of this file.
1 // Copyright (c) 2023-present 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 <bitcoin-build-config.h> // IWYU pragma: keep
7 #include <test/fuzz/fuzz.h>
8 #include <test/fuzz/util.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 // There is an inconsistency in BDB on Windows.
22 // See: https://github.com/bitcoin/bitcoin/pull/26606#issuecomment-2322763212
23 #undef USE_BDB_NON_MSVC
24 #if defined(USE_BDB) && !defined(_MSC_VER)
25 #define USE_BDB_NON_MSVC
26 #endif
27 
30 
31 namespace {
32 TestingSetup* g_setup;
33 } // namespace
34 
36 {
37  static auto testing_setup = MakeNoLogFileContext<TestingSetup>();
38  g_setup = testing_setup.get();
39 }
40 
42 {
43  const auto wallet_path = g_setup->m_args.GetDataDirNet() / "fuzzed_wallet.dat";
44 
45  {
46  AutoFile outfile{fsbridge::fopen(wallet_path, "wb")};
47  outfile << Span{buffer};
48  }
49 
50  const DatabaseOptions options{};
51  DatabaseStatus status;
52  bilingual_str error;
53 
54  fs::path bdb_ro_dumpfile{g_setup->m_args.GetDataDirNet() / "fuzzed_dumpfile_bdb_ro.dump"};
55  if (fs::exists(bdb_ro_dumpfile)) { // Writing into an existing dump file will throw an exception
56  remove(bdb_ro_dumpfile);
57  }
58  g_setup->m_args.ForceSetArg("-dumpfile", fs::PathToString(bdb_ro_dumpfile));
59 
60 #ifdef USE_BDB_NON_MSVC
61  bool bdb_ro_err = false;
62  bool bdb_ro_strict_err = false;
63 #endif
64  auto db{MakeBerkeleyRODatabase(wallet_path, options, status, error)};
65  if (db) {
66  assert(DumpWallet(g_setup->m_args, *db, error));
67  } else {
68 #ifdef USE_BDB_NON_MSVC
69  bdb_ro_err = true;
70 #endif
71  if (error.original.starts_with("AutoFile::ignore: end of file") ||
72  error.original.starts_with("AutoFile::read: end of file") ||
73  error.original.starts_with("AutoFile::seek: ") ||
74  error.original == "Not a BDB file" ||
75  error.original == "Unexpected page type, should be 9 (BTree Metadata)" ||
76  error.original == "Unexpected database flags, should only be 0x20 (subdatabases)" ||
77  error.original == "Unexpected outer database root page type" ||
78  error.original == "Unexpected number of entries in outer database root page" ||
79  error.original == "Subdatabase page number has unexpected length" ||
80  error.original == "Unknown record type in records page" ||
81  error.original == "Unknown record type in internal page" ||
82  error.original == "Unexpected page size" ||
83  error.original == "Unexpected page type" ||
84  error.original == "Page number mismatch" ||
85  error.original == "Bad btree level" ||
86  error.original == "Bad page size" ||
87  error.original == "Meta page number mismatch" ||
88  error.original == "Data record position not in page" ||
89  error.original == "Internal record position not in page" ||
90  error.original == "LSNs are not reset, this database is not completely flushed. Please reopen then close the database with a version that has BDB support" ||
91  error.original == "Records page has odd number of records" ||
92  error.original == "Bad overflow record page type") {
93  // Do nothing
94  } else if (error.original == "Subdatabase last page is greater than database last page" ||
95  error.original == "Page number is greater than database last page" ||
96  error.original == "Last page number could not fit in file" ||
97  error.original == "Subdatabase has an unexpected name" ||
98  error.original == "Unsupported BDB data file version number" ||
99  error.original == "BDB builtin encryption is not supported") {
100 #ifdef USE_BDB_NON_MSVC
101  bdb_ro_strict_err = true;
102 #endif
103  } else {
104  throw std::runtime_error(error.original);
105  }
106  }
107 
108 #ifdef USE_BDB_NON_MSVC
109  // Try opening with BDB
110  fs::path bdb_dumpfile{g_setup->m_args.GetDataDirNet() / "fuzzed_dumpfile_bdb.dump"};
111  if (fs::exists(bdb_dumpfile)) { // Writing into an existing dump file will throw an exception
112  remove(bdb_dumpfile);
113  }
114  g_setup->m_args.ForceSetArg("-dumpfile", fs::PathToString(bdb_dumpfile));
115 
116  try {
117  auto db{MakeBerkeleyDatabase(wallet_path, options, status, error)};
118  if (bdb_ro_err && !db) {
119  return;
120  }
121  assert(db);
122  if (bdb_ro_strict_err) {
123  // BerkeleyRO will be stricter than BDB. Ignore when those specific errors are hit.
124  return;
125  }
126  assert(!bdb_ro_err);
127  assert(DumpWallet(g_setup->m_args, *db, error));
128  } catch (const std::runtime_error& e) {
129  if (bdb_ro_err) return;
130  throw e;
131  }
132 
133  // Make sure the dumpfiles match
134  if (fs::exists(bdb_ro_dumpfile) && fs::exists(bdb_dumpfile)) {
135  std::ifstream bdb_ro_dump(bdb_ro_dumpfile, std::ios_base::binary | std::ios_base::in);
136  std::ifstream bdb_dump(bdb_dumpfile, std::ios_base::binary | std::ios_base::in);
137  assert(std::equal(
138  std::istreambuf_iterator<char>(bdb_ro_dump.rdbuf()),
139  std::istreambuf_iterator<char>(),
140  std::istreambuf_iterator<char>(bdb_dump.rdbuf())));
141  }
142 #endif
143 }
std::unique_ptr< BerkeleyRODatabase > MakeBerkeleyRODatabase(const fs::path &path, const DatabaseOptions &options, DatabaseStatus &status, bilingual_str &error)
Return object giving access to Berkeley Read Only database at specified path.
Definition: migrate.cpp:771
assert(!tx.IsCoinBase())
FILE * fopen(const fs::path &p, const char *mode)
Definition: fs.cpp:26
Bilingual messages:
Definition: translation.h:24
Non-refcounted RAII wrapper for FILE*.
Definition: streams.h:391
FUZZ_TARGET(wallet_bdb_parser,.init=initialize_wallet_bdb_parser)
std::unique_ptr< BerkeleyDatabase > MakeBerkeleyDatabase(const fs::path &path, const DatabaseOptions &options, DatabaseStatus &status, bilingual_str &error)
Return object giving access to Berkeley database at specified path.
Definition: bdb.cpp:948
static std::string PathToString(const path &path)
Convert path object to a byte string.
Definition: fs.h:151
bool DumpWallet(const ArgsManager &args, WalletDatabase &db, bilingual_str &error)
Definition: dump.cpp:24
DatabaseStatus
Definition: db.h:205
std::string original
Definition: translation.h:25
void initialize_wallet_bdb_parser()
A Span is an object that can refer to a contiguous sequence of objects.
Definition: span.h:97
static bool exists(const path &p)
Definition: fs.h:89
Path class wrapper to block calls to the fs::path(std::string) implicit constructor and the fs::path:...
Definition: fs.h:32
Testing setup that configures a complete environment.
Definition: setup_common.h:121