aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/test/fuzz/wallet_bdb_parser.cpp
blob: 24ef75f791ce3e608458058bed7ca21b8995e883 (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
// 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_pgno_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 == "AutoFile::ignore: end of file: iostream error" ||
            error.original == "AutoFile::read: end of file: iostream error" ||
            error.original == "Not a BDB file" ||
            error.original == "Unsupported BDB data file version number" ||
            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 has an unexpected name" ||
            error.original == "Subdatabase page number has unexpected length" ||
            error.original == "Unexpected inner database page type" ||
            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 == "File size is not a multiple of page size" ||
            error.original == "Meta page number mismatch") {
            // 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 == "Page number is greater than subdatabase last page" ||
                   error.original == "Last page number could not fit in file") {
#ifdef USE_BDB
            bdb_ro_pgno_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_pgno_err) {
            // BerkeleyRO will throw on opening for errors involving bad page numbers, but BDB does not.
            // Ignore those.
            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
}