aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/test/fuzz/wallet_bdb_parser.cpp
blob: 5216e09769a876928160cd8db7d2830c89294ee1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Copyright (c) 2023 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/util/setup_common.h>
#include <util/fs.h>
#include <util/time.h>
#include <util/translation.h>
#include <wallet/bdb.h>
#include <wallet/db.h>
#include <wallet/dump.h>
#include <wallet/migrate.h>

#include <fstream>
#include <iostream>

using wallet::DatabaseOptions;
using wallet::DatabaseStatus;

namespace {
TestingSetup* g_setup;
} // namespace

void initialize_wallet_bdb_parser()
{
    static auto testing_setup = MakeNoLogFileContext<TestingSetup>();
    g_setup = testing_setup.get();
}

FUZZ_TARGET(wallet_bdb_parser, .init = initialize_wallet_bdb_parser)
{
    const auto wallet_path = g_setup->m_args.GetDataDirNet() / "fuzzed_wallet.dat";

    {
        AutoFile outfile{fsbridge::fopen(wallet_path, "wb")};
        outfile << Span{buffer};
    }

    const DatabaseOptions options{};
    DatabaseStatus status;
    bilingual_str error;

    fs::path bdb_ro_dumpfile{g_setup->m_args.GetDataDirNet() / "fuzzed_dumpfile_bdb_ro.dump"};
    if (fs::exists(bdb_ro_dumpfile)) { // Writing into an existing dump file will throw an exception
        remove(bdb_ro_dumpfile);
    }
    g_setup->m_args.ForceSetArg("-dumpfile", fs::PathToString(bdb_ro_dumpfile));

#ifdef USE_BDB
    bool bdb_ro_err = false;
    bool bdb_ro_strict_err = false;
#endif
    auto db{MakeBerkeleyRODatabase(wallet_path, options, status, error)};
    if (db) {
        assert(DumpWallet(g_setup->m_args, *db, error));
    } else {
#ifdef USE_BDB
        bdb_ro_err = true;
#endif
        if (error.original.starts_with("AutoFile::ignore: end of file") ||
            error.original.starts_with("AutoFile::read: end of file") ||
            error.original == "Not a BDB file" ||
            error.original == "Unexpected page type, should be 9 (BTree Metadata)" ||
            error.original == "Unexpected database flags, should only be 0x20 (subdatabases)" ||
            error.original == "Unexpected outer database root page type" ||
            error.original == "Unexpected number of entries in outer database root page" ||
            error.original == "Subdatabase page number has unexpected length" ||
            error.original == "Unknown record type in records page" ||
            error.original == "Unknown record type in internal page" ||
            error.original == "Unexpected page size" ||
            error.original == "Unexpected page type" ||
            error.original == "Page number mismatch" ||
            error.original == "Bad btree level" ||
            error.original == "Bad page size" ||
            error.original == "Meta page number mismatch" ||
            error.original == "Data record position not in page" ||
            error.original == "Internal record position not in page" ||
            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" ||
            error.original == "Records page has odd number of records" ||
            error.original == "Bad overflow record page type") {
            // Do nothing
        } else if (error.original == "Subdatabase last page is greater than database last page" ||
                   error.original == "Page number is greater than database last page" ||
                   error.original == "Last page number could not fit in file" ||
                   error.original == "Subdatabase has an unexpected name" ||
                   error.original == "Unsupported BDB data file version number" ||
                   error.original == "BDB builtin encryption is not supported") {
#ifdef USE_BDB
            bdb_ro_strict_err = true;
#endif
        } else {
            throw std::runtime_error(error.original);
        }
    }

#ifdef USE_BDB
    // Try opening with BDB
    fs::path bdb_dumpfile{g_setup->m_args.GetDataDirNet() / "fuzzed_dumpfile_bdb.dump"};
    if (fs::exists(bdb_dumpfile)) { // Writing into an existing dump file will throw an exception
        remove(bdb_dumpfile);
    }
    g_setup->m_args.ForceSetArg("-dumpfile", fs::PathToString(bdb_dumpfile));

    try {
        auto db{MakeBerkeleyDatabase(wallet_path, options, status, error)};
        if (bdb_ro_err && !db) {
            return;
        }
        assert(db);
        if (bdb_ro_strict_err) {
            // BerkeleyRO will be stricter than BDB. Ignore when those specific errors are hit.
            return;
        }
        assert(!bdb_ro_err);
        assert(DumpWallet(g_setup->m_args, *db, error));
    } catch (const std::runtime_error& e) {
        if (bdb_ro_err) return;
        throw e;
    }

    // Make sure the dumpfiles match
    if (fs::exists(bdb_ro_dumpfile) && fs::exists(bdb_dumpfile)) {
        std::ifstream bdb_ro_dump(bdb_ro_dumpfile, std::ios_base::binary | std::ios_base::in);
        std::ifstream bdb_dump(bdb_dumpfile, std::ios_base::binary | std::ios_base::in);
        assert(std::equal(
            std::istreambuf_iterator<char>(bdb_ro_dump.rdbuf()),
            std::istreambuf_iterator<char>(),
            std::istreambuf_iterator<char>(bdb_dump.rdbuf())));
    }
#endif
}