Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fuzz: Add fuzzing harness for LoadMempool(...) and DumpMempool(...) #19259

Merged
merged 3 commits into from Mar 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Makefile.test.include
Expand Up @@ -296,7 +296,8 @@ test_fuzz_fuzz_SOURCES = \
test/fuzz/transaction.cpp \
test/fuzz/tx_in.cpp \
test/fuzz/tx_out.cpp \
test/fuzz/txrequest.cpp
test/fuzz/txrequest.cpp \
test/fuzz/validation_load_mempool.cpp
endif # ENABLE_FUZZ_BINARY

nodist_test_test_bitcoin_SOURCES = $(GENERATED_TEST_FILES)
Expand Down
23 changes: 22 additions & 1 deletion src/test/fuzz/util.h
Expand Up @@ -259,6 +259,16 @@ void SetFuzzedErrNo(FuzzedDataProvider& fuzzed_data_provider, const std::array<T
errno = fuzzed_data_provider.PickValueInArray(errnos);
}

/*
* Sets a fuzzed errno in the range [0, 133 (EHWPOISON)]. Can be used from functions emulating
* standard library functions that set errno, or in other contexts where the value of errno
* might be relevant for the execution path that will be taken.
*/
inline void SetFuzzedErrNo(FuzzedDataProvider& fuzzed_data_provider) noexcept
{
errno = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 133);
}

/**
* Returns a byte vector of specified size regardless of the number of remaining bytes available
* from the fuzzer. Pads with zero value bytes if needed to achieve the specified size.
Expand Down Expand Up @@ -345,6 +355,7 @@ class FuzzedFileProvider

FILE* open()
{
SetFuzzedErrNo(m_fuzzed_data_provider);
if (m_fuzzed_data_provider.ConsumeBool()) {
return nullptr;
}
Expand Down Expand Up @@ -386,6 +397,7 @@ class FuzzedFileProvider
static ssize_t read(void* cookie, char* buf, size_t size)
{
FuzzedFileProvider* fuzzed_file = (FuzzedFileProvider*)cookie;
SetFuzzedErrNo(fuzzed_file->m_fuzzed_data_provider);
if (buf == nullptr || size == 0 || fuzzed_file->m_fuzzed_data_provider.ConsumeBool()) {
return fuzzed_file->m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
}
Expand All @@ -404,6 +416,7 @@ class FuzzedFileProvider
static ssize_t write(void* cookie, const char* buf, size_t size)
{
FuzzedFileProvider* fuzzed_file = (FuzzedFileProvider*)cookie;
SetFuzzedErrNo(fuzzed_file->m_fuzzed_data_provider);
const ssize_t n = fuzzed_file->m_fuzzed_data_provider.ConsumeIntegralInRange<ssize_t>(0, size);
if (AdditionOverflow(fuzzed_file->m_offset, (int64_t)n)) {
return fuzzed_file->m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
Expand All @@ -414,8 +427,9 @@ class FuzzedFileProvider

static int seek(void* cookie, int64_t* offset, int whence)
{
assert(whence == SEEK_SET || whence == SEEK_CUR); // SEEK_END not implemented yet.
assert(whence == SEEK_SET || whence == SEEK_CUR || whence == SEEK_END);
FuzzedFileProvider* fuzzed_file = (FuzzedFileProvider*)cookie;
SetFuzzedErrNo(fuzzed_file->m_fuzzed_data_provider);
int64_t new_offset = 0;
if (whence == SEEK_SET) {
new_offset = *offset;
Expand All @@ -424,6 +438,12 @@ class FuzzedFileProvider
return -1;
}
new_offset = fuzzed_file->m_offset + *offset;
} else if (whence == SEEK_END) {
const int64_t n = fuzzed_file->m_fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, 4096);
if (AdditionOverflow(n, *offset)) {
return -1;
}
new_offset = n + *offset;
}
if (new_offset < 0) {
return -1;
Expand All @@ -436,6 +456,7 @@ class FuzzedFileProvider
static int close(void* cookie)
{
FuzzedFileProvider* fuzzed_file = (FuzzedFileProvider*)cookie;
SetFuzzedErrNo(fuzzed_file->m_fuzzed_data_provider);
return fuzzed_file->m_fuzzed_data_provider.ConsumeIntegralInRange<int>(-1, 0);
}
};
Expand Down
34 changes: 34 additions & 0 deletions src/test/fuzz/validation_load_mempool.cpp
@@ -0,0 +1,34 @@
// Copyright (c) 2020 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 <chainparamsbase.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/util/setup_common.h>
#include <txmempool.h>
#include <util/time.h>
#include <validation.h>

#include <cstdint>
#include <vector>

void initialize_validation_load_mempool()
{
static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
}

FUZZ_TARGET_INIT(validation_load_mempool, initialize_validation_load_mempool)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
SetMockTime(ConsumeTime(fuzzed_data_provider));
FuzzedFileProvider fuzzed_file_provider = ConsumeFile(fuzzed_data_provider);

CTxMemPool pool{};
auto fuzzed_fopen = [&](const fs::path&, const char*) {
return fuzzed_file_provider.open();
};
(void)LoadMempool(pool, ::ChainstateActive(), fuzzed_fopen);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to not introduce a new global

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! Now addressed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not addressed?

nvm. @dongcarl will fix this 😬

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought you meant the introduction of fuzzed_fopen? What did you mean? :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, the ::ChainstateActive()

(void)DumpMempool(pool, fuzzed_fopen, true);
}
10 changes: 5 additions & 5 deletions src/validation.cpp
Expand Up @@ -5010,11 +5010,11 @@ CBlockFileInfo* GetBlockFileInfo(size_t n)

static const uint64_t MEMPOOL_DUMP_VERSION = 1;

bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate)
bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate, FopenFn mockable_fopen_function)
{
const CChainParams& chainparams = Params();
int64_t nExpiryTimeout = gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60;
FILE* filestr = fsbridge::fopen(GetDataDir() / "mempool.dat", "rb");
FILE* filestr{mockable_fopen_function(GetDataDir() / "mempool.dat", "rb")};
CAutoFile file(filestr, SER_DISK, CLIENT_VERSION);
if (file.IsNull()) {
LogPrintf("Failed to open mempool file from disk. Continuing anyway.\n");
Expand Down Expand Up @@ -5095,7 +5095,7 @@ bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate)
return true;
}

bool DumpMempool(const CTxMemPool& pool)
bool DumpMempool(const CTxMemPool& pool, FopenFn mockable_fopen_function, bool skip_file_commit)
{
int64_t start = GetTimeMicros();

Expand All @@ -5118,7 +5118,7 @@ bool DumpMempool(const CTxMemPool& pool)
int64_t mid = GetTimeMicros();

try {
FILE* filestr = fsbridge::fopen(GetDataDir() / "mempool.dat.new", "wb");
FILE* filestr{mockable_fopen_function(GetDataDir() / "mempool.dat.new", "wb")};
if (!filestr) {
return false;
}
Expand All @@ -5141,7 +5141,7 @@ bool DumpMempool(const CTxMemPool& pool)
LogPrintf("Writing %d unbroadcast transactions to disk.\n", unbroadcast_txids.size());
file << unbroadcast_txids;

if (!FileCommit(file.Get()))
if (!skip_file_commit && !FileCommit(file.Get()))
throw std::runtime_error("FileCommit failed");
file.fclose();
if (!RenameOver(GetDataDir() / "mempool.dat.new", GetDataDir() / "mempool.dat")) {
Expand Down
6 changes: 4 additions & 2 deletions src/validation.h
Expand Up @@ -1013,11 +1013,13 @@ int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Para
/** Get block file info entry for one block file */
CBlockFileInfo* GetBlockFileInfo(size_t n);

using FopenFn = std::function<FILE*(const fs::path&, const char*)>;

/** Dump the mempool to disk. */
bool DumpMempool(const CTxMemPool& pool);
bool DumpMempool(const CTxMemPool& pool, FopenFn mockable_fopen_function = fsbridge::fopen, bool skip_file_commit = false);

/** Load the mempool from disk. */
bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate);
bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate, FopenFn mockable_fopen_function = fsbridge::fopen);

//! Check whether the block associated with this index entry is pruned or not.
inline bool IsBlockPruned(const CBlockIndex* pblockindex)
Expand Down