|
| 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