Merge bitcoin/bitcoin#31549: fuzz: Abort if system time is called without mock time being set
Some checks are pending
CI / test each commit (push) Waiting to run
CI / macOS 14 native, arm64, no depends, sqlite only, gui (push) Waiting to run
CI / macOS 14 native, arm64, fuzz (push) Waiting to run
CI / Win64 native, VS 2022 (push) Waiting to run
CI / Win64 native fuzz, VS 2022 (push) Waiting to run
CI / ASan + LSan + UBSan + integer, no depends, USDT (push) Waiting to run

a96b84cb1b fuzz: Abort when calling system time without setting mock time (marcofleon)
ff21870e20 fuzz: Add SetMockTime() to necessary targets (marcofleon)

Pull request description:

  This PR expands the `CheckGlobals` utility that was introduced in https://github.com/bitcoin/bitcoin/pull/31486 and should help with fuzz stability (https://github.com/bitcoin/bitcoin/issues/29018).

  System time shouldn't be used when running a fuzz test, as it is likely to introduce instability (non-determinism). This PR identifies and fixes the targets that were calling system time without setting mock time at the start of an iteration.

  Removing`SetMockTime()` from any one of these targets should result in a crash and a message describing the issue.

ACKs for top commit:
  achow101:
    ACK a96b84cb1b
  dergoegge:
    Code review ACK a96b84cb1b
  brunoerg:
    crACK a96b84cb1b

Tree-SHA512: e093a9feb8a397954f7b1416dfa8790b2733f09d5ac51fda5a9d225a55ebd8f99135aa52bdf5ab531653ad1a3739c4ca2b5349c1d989bb4b009ec8eaad684f7d
This commit is contained in:
Ava Chow 2025-01-09 19:31:07 -05:00
commit 37af8bfb34
No known key found for this signature in database
GPG key ID: 17565732E08E5E41
17 changed files with 65 additions and 5 deletions

View file

@ -116,6 +116,9 @@ void initialize()
// - Creating a BasicTestingSetup or derived class will switch to a random seed.
SeedRandomStateForTest(SeedRand::ZEROS);
// Set time to the genesis block timestamp for deterministic initialization.
SetMockTime(1231006505);
// Terminate immediately if a fuzzing harness ever tries to create a socket.
// Individual tests can override this by pointing CreateSock to a mocked alternative.
CreateSock = [](int, int, int) -> std::unique_ptr<Sock> { std::terminate(); };

View file

@ -9,6 +9,7 @@
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/util/setup_common.h>
#include <util/time.h>
#include <validation.h>
#include <cstdint>
@ -27,6 +28,7 @@ void initialize_load_external_block_file()
FUZZ_TARGET(load_external_block_file, .init = initialize_load_external_block_file)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
SetMockTime(ConsumeTime(fuzzed_data_provider));
FuzzedFileProvider fuzzed_file_provider{fuzzed_data_provider};
AutoFile fuzzed_block_file{fuzzed_file_provider.open()};
if (fuzzed_block_file.IsNull()) {

View file

@ -1,3 +1,7 @@
// Copyright (c) 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 <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
@ -13,6 +17,7 @@
#include <random.h>
#include <txmempool.h>
#include <util/check.h>
#include <util/time.h>
#include <util/translation.h>
#include <deque>
@ -36,6 +41,7 @@ FUZZ_TARGET(mini_miner, .init = initialize_miner)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
SetMockTime(ConsumeTime(fuzzed_data_provider));
bilingual_str error;
CTxMemPool pool{CTxMemPool::Options{}, error};
Assert(error.empty());
@ -115,6 +121,7 @@ FUZZ_TARGET(mini_miner_selection, .init = initialize_miner)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
SetMockTime(ConsumeTime(fuzzed_data_provider));
bilingual_str error;
CTxMemPool pool{CTxMemPool::Options{}, error};
Assert(error.empty());

View file

@ -16,6 +16,7 @@
#include <test/util/setup_common.h>
#include <util/asmap.h>
#include <util/chaintype.h>
#include <util/time.h>
#include <cstdint>
#include <optional>
@ -79,6 +80,7 @@ FUZZ_TARGET(net, .init = initialize_net)
FUZZ_TARGET(local_address, .init = initialize_net)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
SetMockTime(ConsumeTime(fuzzed_data_provider));
CService service{ConsumeService(fuzzed_data_provider)};
CNode node{ConsumeNode(fuzzed_data_provider)};
{

View file

@ -154,14 +154,15 @@ void initialize()
FUZZ_TARGET(p2p_headers_presync, .init = initialize)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
SetMockTime(ConsumeTime(fuzzed_data_provider));
ChainstateManager& chainman = *g_testing_setup->m_node.chainman;
LOCK(NetEventsInterface::g_msgproc_mutex);
g_testing_setup->ResetAndInitialize();
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
CBlockHeader base{chainman.GetParams().GenesisBlock()};
SetMockTime(base.nTime);

View file

@ -11,6 +11,7 @@
#include <test/util/txmempool.h>
#include <txmempool.h>
#include <util/check.h>
#include <util/time.h>
#include <util/translation.h>
#include <cstddef>
@ -46,6 +47,7 @@ FUZZ_TARGET(partially_downloaded_block, .init = initialize_pdb)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
SetMockTime(ConsumeTime(fuzzed_data_provider));
auto block{ConsumeDeserializable<CBlock>(fuzzed_data_provider, TX_WITH_WITNESS)};
if (!block || block->vtx.size() == 0 ||

View file

@ -9,6 +9,7 @@
#include <test/fuzz/util.h>
#include <test/fuzz/util/net.h>
#include <test/util/setup_common.h>
#include <util/time.h>
#include <cstdint>
#include <string>
@ -29,6 +30,7 @@ void initialize_socks5()
FUZZ_TARGET(socks5, .init = initialize_socks5)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
SetMockTime(ConsumeTime(fuzzed_data_provider));
ProxyCredentials proxy_credentials;
proxy_credentials.username = fuzzed_data_provider.ConsumeRandomLengthString(512);
proxy_credentials.password = fuzzed_data_provider.ConsumeRandomLengthString(512);

View file

@ -18,6 +18,7 @@
#include <test/util/txmempool.h>
#include <util/hasher.h>
#include <util/rbf.h>
#include <util/time.h>
#include <txmempool.h>
#include <validation.h>
#include <validationinterface.h>
@ -167,6 +168,7 @@ FUZZ_TARGET(txdownloadman, .init = initialize)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
SetMockTime(ConsumeTime(fuzzed_data_provider));
// Initialize txdownloadman
bilingual_str error;
@ -297,6 +299,7 @@ FUZZ_TARGET(txdownloadman_impl, .init = initialize)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
SetMockTime(ConsumeTime(fuzzed_data_provider));
// Initialize a TxDownloadManagerImpl
bilingual_str error;

View file

@ -5,6 +5,7 @@
#include <test/fuzz/util/check_globals.h>
#include <test/util/random.h>
#include <util/time.h>
#include <iostream>
#include <memory>
@ -16,6 +17,8 @@ struct CheckGlobalsImpl {
{
g_used_g_prng = false;
g_seeded_g_prng_zero = false;
g_used_system_time = false;
SetMockTime(0s);
}
~CheckGlobalsImpl()
{
@ -34,6 +37,19 @@ struct CheckGlobalsImpl {
<< std::endl;
std::abort(); // Abort, because AFL may try to recover from a std::exit
}
if (g_used_system_time) {
std::cerr << "\n\n"
"The current fuzz target accessed system time.\n\n"
"This is acceptable, but requires the fuzz target to call \n"
"SetMockTime() at the beginning of processing the fuzz input.\n\n"
"Without setting mock time, time-dependent behavior can lead \n"
"to non-reproducible bugs or inefficient fuzzing.\n\n"
<< std::endl;
std::abort();
}
}
};

View file

@ -5,10 +5,13 @@
#ifndef BITCOIN_TEST_FUZZ_UTIL_CHECK_GLOBALS_H
#define BITCOIN_TEST_FUZZ_UTIL_CHECK_GLOBALS_H
#include <atomic>
#include <memory>
#include <optional>
#include <string>
extern std::atomic<bool> g_used_system_time;
struct CheckGlobalsImpl;
struct CheckGlobals {
CheckGlobals();

View file

@ -24,6 +24,7 @@
#include <util/check.h>
#include <util/fs.h>
#include <util/result.h>
#include <util/time.h>
#include <validation.h>
#include <cstdint>
@ -72,6 +73,7 @@ void utxo_snapshot_fuzz(FuzzBufferType buffer)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)); // regtest genesis block timestamp
auto& setup{*g_setup};
bool dirty_chainman{false}; // Re-use the global chainman, but reset it when it is dirty
auto& chainman{*setup.m_node.chainman};

View file

@ -15,6 +15,7 @@
#include <test/util/mining.h>
#include <test/util/setup_common.h>
#include <util/chaintype.h>
#include <util/time.h>
#include <validation.h>
using node::BlockAssembler;
@ -22,18 +23,22 @@ using node::BlockAssembler;
FUZZ_TARGET(utxo_total_supply)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
const auto mock_time{ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)}; // regtest genesis block timestamp
/** The testing setup that creates a chainman only (no chainstate) */
ChainTestingSetup test_setup{
ChainType::REGTEST,
{
.extra_args = {"-testactivationheight=bip34@2"},
.extra_args = {
"-testactivationheight=bip34@2",
strprintf("-mocktime=%d", mock_time).c_str()
},
},
};
// Create chainstate
test_setup.LoadVerifyActivateChainstate();
auto& node{test_setup.m_node};
auto& chainman{*Assert(test_setup.m_node.chainman)};
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
const auto ActiveHeight = [&]() {
LOCK(chainman.GetMutex());

View file

@ -199,7 +199,9 @@ BasicTestingSetup::~BasicTestingSetup()
{
m_node.ecc_context.reset();
m_node.kernel.reset();
SetMockTime(0s); // Reset mocktime for following tests
if constexpr (!G_FUZZING) {
SetMockTime(0s); // Reset mocktime for following tests
}
LogInstance().DisconnectTestLogger();
if (m_has_custom_datadir) {
// Only remove the lock file, preserve the data directory.

View file

@ -20,10 +20,14 @@
void UninterruptibleSleep(const std::chrono::microseconds& n) { std::this_thread::sleep_for(n); }
static std::atomic<std::chrono::seconds> g_mock_time{}; //!< For testing
std::atomic<bool> g_used_system_time{false};
NodeClock::time_point NodeClock::now() noexcept
{
const auto mocktime{g_mock_time.load(std::memory_order_relaxed)};
if (!mocktime.count()) {
g_used_system_time = true;
}
const auto ret{
mocktime.count() ?
mocktime :

View file

@ -24,6 +24,7 @@
#include <uint256.h>
#include <util/check.h>
#include <util/result.h>
#include <util/time.h>
#include <util/translation.h>
#include <wallet/coincontrol.h>
#include <wallet/context.h>
@ -58,6 +59,7 @@ FUZZ_TARGET(wallet_notifications, .init = initialize_setup)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
SetMockTime(ConsumeTime(fuzzed_data_provider));
// The total amount, to be distributed to the wallets a and b in txs
// without fee. Thus, the balance of the wallets should always equal the
// total amount.

View file

@ -19,6 +19,7 @@
#include <test/fuzz/util/descriptor.h>
#include <test/util/setup_common.h>
#include <util/check.h>
#include <util/time.h>
#include <util/translation.h>
#include <validation.h>
#include <wallet/scriptpubkeyman.h>
@ -87,6 +88,7 @@ FUZZ_TARGET(scriptpubkeyman, .init = initialize_spkm)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
SetMockTime(ConsumeTime(fuzzed_data_provider));
const auto& node{g_setup->m_node};
Chainstate& chainstate{node.chainman->ActiveChainstate()};
std::unique_ptr<CWallet> wallet_ptr{std::make_unique<CWallet>(node.chain.get(), "", CreateMockableWalletDatabase())};

View file

@ -8,6 +8,7 @@
#include <test/fuzz/util/wallet.h>
#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <util/time.h>
#include <wallet/coincontrol.h>
#include <wallet/context.h>
#include <wallet/spend.h>
@ -32,6 +33,7 @@ FUZZ_TARGET(wallet_create_transaction, .init = initialize_setup)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
SetMockTime(ConsumeTime(fuzzed_data_provider));
const auto& node = g_setup->m_node;
Chainstate& chainstate{node.chainman->ActiveChainstate()};
ArgsManager& args = *node.args;