Skip to content

Commit

Permalink
fuzz: Abort when using global PRNG without re-seed
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcoFalke committed Dec 16, 2024
1 parent fa7809a commit fa18acb
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 2 deletions.
2 changes: 2 additions & 0 deletions src/random.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -671,9 +671,11 @@ void MakeRandDeterministicDANGEROUS(const uint256& seed) noexcept
{
GetRNGState().MakeDeterministic(seed);
}
std::atomic<bool> g_used_g_prng{false}; // Only accessed from tests

void GetRandBytes(Span<unsigned char> bytes) noexcept
{
g_used_g_prng = true;
ProcRand(bytes.data(), bytes.size(), RNGLevel::FAST, /*always_use_real_rng=*/false);
}

Expand Down
9 changes: 7 additions & 2 deletions src/test/fuzz/fuzz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <netaddress.h>
#include <netbase.h>
#include <test/fuzz/util/check_globals.h>
#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <util/check.h>
Expand Down Expand Up @@ -78,6 +79,12 @@ void FuzzFrameworkRegisterTarget(std::string_view name, TypeTestOneInput target,
static std::string_view g_fuzz_target;
static const TypeTestOneInput* g_test_one_input{nullptr};

inline void test_one_input(FuzzBufferType buffer)
{
CheckGlobals check{};
(*Assert(g_test_one_input))(buffer);
}

const std::function<std::string()> G_TEST_GET_FULL_NAME{[]{
return std::string{g_fuzz_target};
}};
Expand Down Expand Up @@ -210,7 +217,6 @@ void signal_handler(int signal)
// This function is used by libFuzzer
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{
static const auto& test_one_input = *Assert(g_test_one_input);
test_one_input({data, size});
return 0;
}
Expand All @@ -227,7 +233,6 @@ extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
int main(int argc, char** argv)
{
initialize();
static const auto& test_one_input = *Assert(g_test_one_input);
#ifdef __AFL_LOOP
// Enable AFL persistent mode. Requires compilation using afl-clang-fast++.
// See fuzzing.md for details.
Expand Down
1 change: 1 addition & 0 deletions src/test/fuzz/util/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# file COPYING or https://opensource.org/license/mit/.

add_library(test_fuzz STATIC EXCLUDE_FROM_ALL
check_globals.cpp
descriptor.cpp
mempool.cpp
net.cpp
Expand Down
41 changes: 41 additions & 0 deletions src/test/fuzz/util/check_globals.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) 2024-present 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/util/check_globals.h>

#include <test/util/random.h>

#include <iostream>
#include <memory>
#include <optional>
#include <string>

struct CheckGlobalsImpl {
CheckGlobalsImpl()
{
g_used_g_prng = false;
g_seeded_g_prng_zero = false;
}
~CheckGlobalsImpl()
{
if (g_used_g_prng && !g_seeded_g_prng_zero) {
std::cerr << "\n\n"
"The current fuzz target used the global random state.\n\n"

"This is acceptable, but requires the fuzz target to call \n"
"SeedRandomStateForTest(SeedRand::ZEROS) at the beginning \n"
"of processing the fuzz input.\n\n"

"An alternative solution would be to avoid any use of globals.\n\n"

"Without a solution, fuzz stability and determinism can lead \n"
"to non-reproducible bugs or inefficient fuzzing.\n\n"
<< std::endl;
std::abort(); // Abort, because AFL may try to recover from a std::exit
}
}
};

CheckGlobals::CheckGlobals() : m_impl(std::make_unique<CheckGlobalsImpl>()) {}
CheckGlobals::~CheckGlobals() = default;
19 changes: 19 additions & 0 deletions src/test/fuzz/util/check_globals.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) 2024-present The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_TEST_FUZZ_UTIL_CHECK_GLOBALS_H
#define BITCOIN_TEST_FUZZ_UTIL_CHECK_GLOBALS_H

#include <memory>
#include <optional>
#include <string>

struct CheckGlobalsImpl;
struct CheckGlobals {
CheckGlobals();
~CheckGlobals();
std::unique_ptr<CheckGlobalsImpl> m_impl;
};

#endif // BITCOIN_TEST_FUZZ_UTIL_CHECK_GLOBALS_H
3 changes: 3 additions & 0 deletions src/test/util/random.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <cstdlib>
#include <iostream>

std::atomic<bool> g_seeded_g_prng_zero{false};

extern void MakeRandDeterministicDANGEROUS(const uint256& seed) noexcept;

void SeedRandomStateForTest(SeedRand seedtype)
Expand All @@ -36,6 +38,7 @@ void SeedRandomStateForTest(SeedRand seedtype)
return GetRandHash();
}();

g_seeded_g_prng_zero = seedtype == SeedRand::ZEROS;
const uint256& seed{seedtype == SeedRand::FIXED_SEED ? ctx_seed : uint256::ZERO};
LogInfo("Setting random seed for current tests to %s=%s\n", RANDOM_CTX_SEED, seed.GetHex());
MakeRandDeterministicDANGEROUS(seed);
Expand Down
4 changes: 4 additions & 0 deletions src/test/util/random.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <random.h>
#include <uint256.h>

#include <atomic>
#include <cstdint>

enum class SeedRand {
Expand All @@ -27,6 +28,9 @@ enum class SeedRand {
/** Seed the global RNG state for testing and log the seed value. This affects all randomness, except GetStrongRandBytes(). */
void SeedRandomStateForTest(SeedRand seed);

extern std::atomic<bool> g_seeded_g_prng_zero;
extern std::atomic<bool> g_used_g_prng;

template <RandomNumberGenerator Rng>
inline CAmount RandMoney(Rng&& rng)
{
Expand Down

0 comments on commit fa18acb

Please sign in to comment.