Skip to content

Commit 8b868f1

Browse files
author
MarcoFalke
committed
fuzz: BIP 42, BIP 30, CVE-2018-17144
1 parent fa23ce4 commit 8b868f1

File tree

3 files changed

+170
-5
lines changed

3 files changed

+170
-5
lines changed

src/Makefile.test.include

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ FUZZ_TARGETS = \
3838
test/fuzz/out_point_deserialize \
3939
test/fuzz/parse_hd_keypath \
4040
test/fuzz/parse_iso8601 \
41-
test/fuzz/partial_merkle_tree_deserialize \
42-
test/fuzz/partially_signed_transaction_deserialize \
43-
test/fuzz/prefilled_transaction_deserialize \
4441
test/fuzz/parse_numbers \
4542
test/fuzz/parse_script \
4643
test/fuzz/parse_univalue \
44+
test/fuzz/partial_merkle_tree_deserialize \
45+
test/fuzz/partially_signed_transaction_deserialize \
46+
test/fuzz/prefilled_transaction_deserialize \
4747
test/fuzz/psbt \
4848
test/fuzz/psbt_input_deserialize \
4949
test/fuzz/psbt_output_deserialize \
@@ -59,7 +59,8 @@ FUZZ_TARGETS = \
5959
test/fuzz/tx_in_deserialize \
6060
test/fuzz/tx_out \
6161
test/fuzz/txoutcompressor_deserialize \
62-
test/fuzz/txundo_deserialize
62+
test/fuzz/txundo_deserialize \
63+
test/fuzz/utxo_total_supply
6364

6465
if ENABLE_FUZZ
6566
noinst_PROGRAMS += $(FUZZ_TARGETS:=)
@@ -418,6 +419,12 @@ test_fuzz_blocktransactionsrequest_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_F
418419
test_fuzz_blocktransactionsrequest_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
419420
test_fuzz_blocktransactionsrequest_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON)
420421

422+
test_fuzz_utxo_total_supply_SOURCES = $(FUZZ_SUITE) test/fuzz/utxo_total_supply.cpp
423+
test_fuzz_utxo_total_supply_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
424+
test_fuzz_utxo_total_supply_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
425+
test_fuzz_utxo_total_supply_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
426+
test_fuzz_utxo_total_supply_LDADD = $(FUZZ_SUITE_LD_COMMON)
427+
421428
test_fuzz_transaction_SOURCES = $(FUZZ_SUITE) test/fuzz/transaction.cpp
422429
test_fuzz_transaction_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
423430
test_fuzz_transaction_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)

src/bench/block_assemble.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ static void AssembleBlock(benchmark::State& state)
3030
std::array<CTransactionRef, NUM_BLOCKS - COINBASE_MATURITY + 1> txs;
3131
for (size_t b{0}; b < NUM_BLOCKS; ++b) {
3232
CMutableTransaction tx;
33-
tx.vin.push_back(MineBlock(g_testing_setup->m_node, SCRIPT_PUB));
33+
tx.vin.push_back(CTxIn{MineBlock(g_testing_setup->m_node, SCRIPT_PUB)});
3434
tx.vin.back().scriptWitness = witness;
3535
tx.vout.emplace_back(1337, SCRIPT_PUB);
3636
if (NUM_BLOCKS - b >= COINBASE_MATURITY)
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Copyright (c) 2020 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <chainparams.h>
6+
#include <consensus/consensus.h>
7+
#include <consensus/merkle.h>
8+
#include <node/coinstats.h>
9+
#include <script/interpreter.h>
10+
#include <streams.h>
11+
#include <test/fuzz/FuzzedDataProvider.h>
12+
#include <test/fuzz/fuzz.h>
13+
#include <test/util/mining.h>
14+
#include <test/util/setup_common.h>
15+
#include <validation.h>
16+
#include <version.h>
17+
18+
enum class Action : uint8_t {
19+
CREATE_INPUT, //!< Append an input-output pair to the last tx in the current block
20+
CREATE_TX, //!< Append a tx to the list of txs in the current block
21+
CREATE_BLOCK, //!< Append the current block to the active chain
22+
};
23+
24+
25+
void test_one_input(const std::vector<uint8_t>& buffer)
26+
{
27+
/** The testing setup that creates a chainstate and other globals */
28+
TestingSetup test_setup{CBaseChainParams::REGTEST};
29+
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
30+
31+
const auto PrepareNextBlock = [&]() {
32+
// Use OP_FALSE to avoid BIP30 check from hitting early
33+
auto block = PrepareBlock(test_setup.m_node, /* coinbase_scriptPubKey */ OP_FALSE);
34+
// Replace OP_FALSE with OP_TRUE
35+
{
36+
CMutableTransaction tx{*block->vtx.back()};
37+
tx.vout.at(0).scriptPubKey = OP_TRUE;
38+
block->vtx.back() = MakeTransactionRef(tx);
39+
}
40+
return block;
41+
};
42+
43+
/** The block template this fuzzer is working on */
44+
auto current_block = PrepareNextBlock();
45+
/** Append-only set of tx outpoints, entries are not removed when spent */
46+
std::vector<std::pair<COutPoint, CTxOut>> txos;
47+
/** The utxo stats at the chain tip */
48+
CCoinsStats utxo_stats;
49+
/** The total amount of coins in the utxo set */
50+
CAmount circulation{0};
51+
52+
53+
// Store the tx out in the txo map
54+
const auto StoreLastTxo = [&]() {
55+
// get last tx
56+
const CTransaction& tx = *current_block->vtx.back();
57+
// get last out
58+
const uint32_t i = tx.vout.size() - 1;
59+
// store it
60+
txos.emplace_back(COutPoint{tx.GetHash(), i}, tx.vout.at(i));
61+
if (current_block->vtx.size() == 1 && tx.vout.at(i).scriptPubKey[0] == OP_RETURN) {
62+
// also store coinbase
63+
const uint32_t i = tx.vout.size() - 2;
64+
txos.emplace_back(COutPoint{tx.GetHash(), i}, tx.vout.at(i));
65+
}
66+
};
67+
const auto AppendRandomTxo = [&](CMutableTransaction& tx) {
68+
const auto& txo = txos.at(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, txos.size() - 1));
69+
tx.vin.emplace_back(txo.first);
70+
tx.vout.emplace_back(txo.second.nValue, txo.second.scriptPubKey); // "Forward" coin with no fee
71+
};
72+
const auto UpdateUtxoStats = [&]() {
73+
LOCK(cs_main);
74+
::ChainstateActive().ForceFlushStateToDisk();
75+
assert(GetUTXOStats(&ChainstateActive().CoinsDB(), utxo_stats));
76+
// Check that miner can't print more money than they are allowed to
77+
assert(circulation == utxo_stats.nTotalAmount);
78+
};
79+
80+
81+
// Update internal state to chain tip
82+
StoreLastTxo();
83+
UpdateUtxoStats();
84+
assert(ChainActive().Height() == 0);
85+
// Get at which height we duplicate the coinbase
86+
// Assuming that the fuzzer will mine relatively short chains (less than 200 blocks), we want the duplicate coinbase to be not too high.
87+
// Up to 2000 seems reasonable.
88+
int64_t duplicate_coinbase_height = fuzzed_data_provider.ConsumeIntegralInRange(0, 20 * COINBASE_MATURITY);
89+
// Always pad with OP_0 at the end to avoid bad-cb-length error
90+
const CScript duplicate_coinbase_script = CScript() << duplicate_coinbase_height << OP_0;
91+
// Mine the first block with this duplicate
92+
current_block = PrepareNextBlock();
93+
StoreLastTxo();
94+
95+
{
96+
// Create duplicate (CScript should match exact format as in CreateNewBlock)
97+
CMutableTransaction tx{*current_block->vtx.front()};
98+
tx.vin.at(0).scriptSig = duplicate_coinbase_script;
99+
100+
// Mine block and create next block template
101+
current_block->vtx.front() = MakeTransactionRef(tx);
102+
}
103+
current_block->hashMerkleRoot = BlockMerkleRoot(*current_block);
104+
assert(!MineBlock(current_block).IsNull());
105+
circulation += GetBlockSubsidy(ChainActive().Height(), Params().GetConsensus());
106+
107+
assert(ChainActive().Height() == 1);
108+
UpdateUtxoStats();
109+
current_block = PrepareNextBlock();
110+
StoreLastTxo();
111+
112+
while (fuzzed_data_provider.remaining_bytes()) {
113+
const auto action = static_cast<Action>(fuzzed_data_provider.ConsumeIntegralInRange(0, 2));
114+
switch (action) {
115+
case Action::CREATE_INPUT: {
116+
CMutableTransaction tx{*current_block->vtx.back()};
117+
AppendRandomTxo(tx);
118+
current_block->vtx.back() = MakeTransactionRef(tx);
119+
StoreLastTxo();
120+
break;
121+
}
122+
case Action::CREATE_TX: {
123+
CMutableTransaction tx{};
124+
AppendRandomTxo(tx);
125+
current_block->vtx.push_back(MakeTransactionRef(tx));
126+
StoreLastTxo();
127+
break;
128+
}
129+
case Action::CREATE_BLOCK: {
130+
ReGenerateCommitments(*current_block);
131+
const bool was_valid = !MineBlock(current_block).IsNull();
132+
133+
const auto prev_utxo_stats = utxo_stats;
134+
if (was_valid) {
135+
circulation += GetBlockSubsidy(ChainActive().Height(), Params().GetConsensus());
136+
137+
if (duplicate_coinbase_height == ChainActive().Height()) {
138+
// we mined the duplicate coinbase
139+
assert(current_block->vtx.at(0)->vin.at(0).scriptSig == duplicate_coinbase_script);
140+
}
141+
}
142+
143+
UpdateUtxoStats();
144+
145+
if (!was_valid) {
146+
// utxo stats must not change
147+
assert(prev_utxo_stats.hashSerialized == utxo_stats.hashSerialized);
148+
}
149+
150+
current_block = PrepareNextBlock();
151+
StoreLastTxo();
152+
break;
153+
}
154+
default:
155+
assert(false);
156+
}
157+
}
158+
}

0 commit comments

Comments
 (0)