Coverage Report

Created: 2025-02-21 14:37

/root/bitcoin/src/wallet/test/fuzz/wallet_bdb_parser.cpp
Line
Count
Source (jump to first uncovered line)
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
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
// 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
28
using wallet::DatabaseOptions;
29
using wallet::DatabaseStatus;
30
31
namespace {
32
TestingSetup* g_setup;
33
} // namespace
34
35
void initialize_wallet_bdb_parser()
36
0
{
37
0
    static auto testing_setup = MakeNoLogFileContext<TestingSetup>();
38
0
    g_setup = testing_setup.get();
39
0
}
40
41
FUZZ_TARGET(wallet_bdb_parser, .init = initialize_wallet_bdb_parser)
42
0
{
43
0
    const auto wallet_path = g_setup->m_args.GetDataDirNet() / "fuzzed_wallet.dat";
44
45
0
    {
46
0
        AutoFile outfile{fsbridge::fopen(wallet_path, "wb")};
47
0
        outfile << Span{buffer};
48
0
    }
49
50
0
    const DatabaseOptions options{};
51
0
    DatabaseStatus status;
52
0
    bilingual_str error;
53
54
0
    fs::path bdb_ro_dumpfile{g_setup->m_args.GetDataDirNet() / "fuzzed_dumpfile_bdb_ro.dump"};
55
0
    if (fs::exists(bdb_ro_dumpfile)) { // Writing into an existing dump file will throw an exception
56
0
        remove(bdb_ro_dumpfile);
57
0
    }
58
0
    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
0
    auto db{MakeBerkeleyRODatabase(wallet_path, options, status, error)};
65
0
    if (db) {
66
0
        assert(DumpWallet(g_setup->m_args, *db, error));
67
0
    } else {
68
#ifdef USE_BDB_NON_MSVC
69
        bdb_ro_err = true;
70
#endif
71
0
        if (error.original.starts_with("AutoFile::ignore: end of file") ||
72
0
            error.original.starts_with("AutoFile::read: end of file") ||
73
0
            error.original.starts_with("AutoFile::seek: ") ||
74
0
            error.original == "Not a BDB file" ||
75
0
            error.original == "Unexpected page type, should be 9 (BTree Metadata)" ||
76
0
            error.original == "Unexpected database flags, should only be 0x20 (subdatabases)" ||
77
0
            error.original == "Unexpected outer database root page type" ||
78
0
            error.original == "Unexpected number of entries in outer database root page" ||
79
0
            error.original == "Subdatabase page number has unexpected length" ||
80
0
            error.original == "Unknown record type in records page" ||
81
0
            error.original == "Unknown record type in internal page" ||
82
0
            error.original == "Unexpected page size" ||
83
0
            error.original == "Unexpected page type" ||
84
0
            error.original == "Page number mismatch" ||
85
0
            error.original == "Bad btree level" ||
86
0
            error.original == "Bad page size" ||
87
0
            error.original == "Meta page number mismatch" ||
88
0
            error.original == "Data record position not in page" ||
89
0
            error.original == "Internal record position not in page" ||
90
0
            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
0
            error.original == "Records page has odd number of records" ||
92
0
            error.original == "Bad overflow record page type") {
93
            // Do nothing
94
0
        } else if (error.original == "Subdatabase last page is greater than database last page" ||
95
0
                   error.original == "Page number is greater than database last page" ||
96
0
                   error.original == "Last page number could not fit in file" ||
97
0
                   error.original == "Subdatabase has an unexpected name" ||
98
0
                   error.original == "Unsupported BDB data file version number" ||
99
0
                   error.original == "BDB builtin encryption is not supported") {
100
#ifdef USE_BDB_NON_MSVC
101
            bdb_ro_strict_err = true;
102
#endif
103
0
        } else {
104
0
            throw std::runtime_error(error.original);
105
0
        }
106
0
    }
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
0
}