/root/bitcoin/src/test/fuzz/util.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2021-present 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 <consensus/amount.h> |
6 | | #include <pubkey.h> |
7 | | #include <test/fuzz/util.h> |
8 | | #include <test/util/script.h> |
9 | | #include <util/check.h> |
10 | | #include <util/overflow.h> |
11 | | #include <util/rbf.h> |
12 | | #include <util/time.h> |
13 | | |
14 | | #include <memory> |
15 | | |
16 | | std::vector<uint8_t> ConstructPubKeyBytes(FuzzedDataProvider& fuzzed_data_provider, std::span<const uint8_t> byte_data, const bool compressed) noexcept |
17 | 0 | { |
18 | 0 | uint8_t pk_type; |
19 | 0 | if (compressed) { |
20 | 0 | pk_type = fuzzed_data_provider.PickValueInArray({0x02, 0x03}); |
21 | 0 | } else { |
22 | 0 | pk_type = fuzzed_data_provider.PickValueInArray({0x04, 0x06, 0x07}); |
23 | 0 | } |
24 | 0 | std::vector<uint8_t> pk_data{byte_data.begin(), byte_data.begin() + (compressed ? CPubKey::COMPRESSED_SIZE : CPubKey::SIZE)}; |
25 | 0 | pk_data[0] = pk_type; |
26 | 0 | return pk_data; |
27 | 0 | } |
28 | | |
29 | | CAmount ConsumeMoney(FuzzedDataProvider& fuzzed_data_provider, const std::optional<CAmount>& max) noexcept |
30 | 0 | { |
31 | 0 | return fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, max.value_or(MAX_MONEY)); |
32 | 0 | } |
33 | | |
34 | | int64_t ConsumeTime(FuzzedDataProvider& fuzzed_data_provider, const std::optional<int64_t>& min, const std::optional<int64_t>& max) noexcept |
35 | 0 | { |
36 | | // Avoid t=0 (1970-01-01T00:00:00Z) since SetMockTime(0) disables mocktime. |
37 | 0 | static const int64_t time_min{ParseISO8601DateTime("2000-01-01T00:00:01Z").value()}; |
38 | 0 | static const int64_t time_max{ParseISO8601DateTime("2100-12-31T23:59:59Z").value()}; |
39 | 0 | return fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(min.value_or(time_min), max.value_or(time_max)); |
40 | 0 | } |
41 | | |
42 | | CMutableTransaction ConsumeTransaction(FuzzedDataProvider& fuzzed_data_provider, const std::optional<std::vector<Txid>>& prevout_txids, const int max_num_in, const int max_num_out) noexcept |
43 | 0 | { |
44 | 0 | CMutableTransaction tx_mut; |
45 | 0 | const auto p2wsh_op_true = fuzzed_data_provider.ConsumeBool(); |
46 | 0 | tx_mut.version = fuzzed_data_provider.ConsumeBool() ? |
47 | 0 | CTransaction::CURRENT_VERSION : |
48 | 0 | fuzzed_data_provider.ConsumeIntegral<uint32_t>(); |
49 | 0 | tx_mut.nLockTime = fuzzed_data_provider.ConsumeIntegral<uint32_t>(); |
50 | 0 | const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, max_num_in); |
51 | 0 | const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, max_num_out); |
52 | 0 | for (int i = 0; i < num_in; ++i) { |
53 | 0 | const auto& txid_prev = prevout_txids ? |
54 | 0 | PickValue(fuzzed_data_provider, *prevout_txids) : |
55 | 0 | Txid::FromUint256(ConsumeUInt256(fuzzed_data_provider)); |
56 | 0 | const auto index_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, max_num_out); |
57 | 0 | const auto sequence = ConsumeSequence(fuzzed_data_provider); |
58 | 0 | const auto script_sig = p2wsh_op_true ? CScript{} : ConsumeScript(fuzzed_data_provider); |
59 | 0 | CScriptWitness script_wit; |
60 | 0 | if (p2wsh_op_true) { |
61 | 0 | script_wit.stack = std::vector<std::vector<uint8_t>>{WITNESS_STACK_ELEM_OP_TRUE}; |
62 | 0 | } else { |
63 | 0 | script_wit = ConsumeScriptWitness(fuzzed_data_provider); |
64 | 0 | } |
65 | 0 | CTxIn in; |
66 | 0 | in.prevout = COutPoint{txid_prev, index_out}; |
67 | 0 | in.nSequence = sequence; |
68 | 0 | in.scriptSig = script_sig; |
69 | 0 | in.scriptWitness = script_wit; |
70 | |
|
71 | 0 | tx_mut.vin.push_back(in); |
72 | 0 | } |
73 | 0 | for (int i = 0; i < num_out; ++i) { |
74 | 0 | const auto amount = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-10, 50 * COIN + 10); |
75 | 0 | const auto script_pk = p2wsh_op_true ? |
76 | 0 | P2WSH_OP_TRUE : |
77 | 0 | ConsumeScript(fuzzed_data_provider, /*maybe_p2wsh=*/true); |
78 | 0 | tx_mut.vout.emplace_back(amount, script_pk); |
79 | 0 | } |
80 | 0 | return tx_mut; |
81 | 0 | } |
82 | | |
83 | | CScriptWitness ConsumeScriptWitness(FuzzedDataProvider& fuzzed_data_provider, const size_t max_stack_elem_size) noexcept |
84 | 0 | { |
85 | 0 | CScriptWitness ret; |
86 | 0 | const auto n_elements = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, max_stack_elem_size); |
87 | 0 | for (size_t i = 0; i < n_elements; ++i) { |
88 | 0 | ret.stack.push_back(ConsumeRandomLengthByteVector(fuzzed_data_provider)); |
89 | 0 | } |
90 | 0 | return ret; |
91 | 0 | } |
92 | | |
93 | | CScript ConsumeScript(FuzzedDataProvider& fuzzed_data_provider, const bool maybe_p2wsh) noexcept |
94 | 0 | { |
95 | 0 | CScript r_script{}; |
96 | 0 | { |
97 | | // Keep a buffer of bytes to allow the fuzz engine to produce smaller |
98 | | // inputs to generate CScripts with repeated data. |
99 | 0 | static constexpr unsigned MAX_BUFFER_SZ{128}; |
100 | 0 | std::vector<uint8_t> buffer(MAX_BUFFER_SZ, uint8_t{'a'}); |
101 | 0 | while (fuzzed_data_provider.ConsumeBool()) { |
102 | 0 | CallOneOf( |
103 | 0 | fuzzed_data_provider, |
104 | 0 | [&] { |
105 | | // Insert byte vector directly to allow malformed or unparsable scripts |
106 | 0 | r_script.insert(r_script.end(), buffer.begin(), buffer.begin() + fuzzed_data_provider.ConsumeIntegralInRange(0U, MAX_BUFFER_SZ)); |
107 | 0 | }, |
108 | 0 | [&] { |
109 | | // Push a byte vector from the buffer |
110 | 0 | r_script << std::vector<uint8_t>{buffer.begin(), buffer.begin() + fuzzed_data_provider.ConsumeIntegralInRange(0U, MAX_BUFFER_SZ)}; |
111 | 0 | }, |
112 | 0 | [&] { |
113 | | // Push multisig |
114 | | // There is a special case for this to aid the fuzz engine |
115 | | // navigate the highly structured multisig format. |
116 | 0 | r_script << fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, 22); |
117 | 0 | int num_data{fuzzed_data_provider.ConsumeIntegralInRange(1, 22)}; |
118 | 0 | while (num_data--) { |
119 | 0 | auto pubkey_bytes{ConstructPubKeyBytes(fuzzed_data_provider, buffer, fuzzed_data_provider.ConsumeBool())}; |
120 | 0 | if (fuzzed_data_provider.ConsumeBool()) { |
121 | 0 | pubkey_bytes.back() = num_data; // Make each pubkey different |
122 | 0 | } |
123 | 0 | r_script << pubkey_bytes; |
124 | 0 | } |
125 | 0 | r_script << fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, 22); |
126 | 0 | }, |
127 | 0 | [&] { |
128 | | // Mutate the buffer |
129 | 0 | const auto vec{ConsumeRandomLengthByteVector(fuzzed_data_provider, /*max_length=*/MAX_BUFFER_SZ)}; |
130 | 0 | std::copy(vec.begin(), vec.end(), buffer.begin()); |
131 | 0 | }, |
132 | 0 | [&] { |
133 | | // Push an integral |
134 | 0 | r_script << fuzzed_data_provider.ConsumeIntegral<int64_t>(); |
135 | 0 | }, |
136 | 0 | [&] { |
137 | | // Push an opcode |
138 | 0 | r_script << ConsumeOpcodeType(fuzzed_data_provider); |
139 | 0 | }, |
140 | 0 | [&] { |
141 | | // Push a scriptnum |
142 | 0 | r_script << ConsumeScriptNum(fuzzed_data_provider); |
143 | 0 | }); |
144 | 0 | } |
145 | 0 | } |
146 | 0 | if (maybe_p2wsh && fuzzed_data_provider.ConsumeBool()) { |
147 | 0 | uint256 script_hash; |
148 | 0 | CSHA256().Write(r_script.data(), r_script.size()).Finalize(script_hash.begin()); |
149 | 0 | r_script.clear(); |
150 | 0 | r_script << OP_0 << ToByteVector(script_hash); |
151 | 0 | } |
152 | 0 | return r_script; |
153 | 0 | } |
154 | | |
155 | | uint32_t ConsumeSequence(FuzzedDataProvider& fuzzed_data_provider) noexcept |
156 | 0 | { |
157 | 0 | return fuzzed_data_provider.ConsumeBool() ? |
158 | 0 | fuzzed_data_provider.PickValueInArray({ |
159 | 0 | CTxIn::SEQUENCE_FINAL, |
160 | 0 | CTxIn::MAX_SEQUENCE_NONFINAL, |
161 | 0 | MAX_BIP125_RBF_SEQUENCE, |
162 | 0 | }) : |
163 | 0 | fuzzed_data_provider.ConsumeIntegral<uint32_t>(); |
164 | 0 | } |
165 | | |
166 | | std::map<COutPoint, Coin> ConsumeCoins(FuzzedDataProvider& fuzzed_data_provider) noexcept |
167 | 0 | { |
168 | 0 | std::map<COutPoint, Coin> coins; |
169 | 0 | LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) { |
170 | 0 | const std::optional<COutPoint> outpoint{ConsumeDeserializable<COutPoint>(fuzzed_data_provider)}; |
171 | 0 | if (!outpoint) { |
172 | 0 | break; |
173 | 0 | } |
174 | 0 | const std::optional<Coin> coin{ConsumeDeserializable<Coin>(fuzzed_data_provider)}; |
175 | 0 | if (!coin) { |
176 | 0 | break; |
177 | 0 | } |
178 | 0 | coins[*outpoint] = *coin; |
179 | 0 | } |
180 | |
|
181 | 0 | return coins; |
182 | 0 | } |
183 | | |
184 | | CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) noexcept |
185 | 0 | { |
186 | 0 | CTxDestination tx_destination; |
187 | 0 | const size_t call_size{CallOneOf( |
188 | 0 | fuzzed_data_provider, |
189 | 0 | [&] { |
190 | 0 | tx_destination = CNoDestination{}; |
191 | 0 | }, |
192 | 0 | [&] { |
193 | 0 | bool compressed = fuzzed_data_provider.ConsumeBool(); |
194 | 0 | CPubKey pk{ConstructPubKeyBytes( |
195 | 0 | fuzzed_data_provider, |
196 | 0 | ConsumeFixedLengthByteVector(fuzzed_data_provider, (compressed ? CPubKey::COMPRESSED_SIZE : CPubKey::SIZE)), |
197 | 0 | compressed |
198 | 0 | )}; |
199 | 0 | tx_destination = PubKeyDestination{pk}; |
200 | 0 | }, |
201 | 0 | [&] { |
202 | 0 | tx_destination = PKHash{ConsumeUInt160(fuzzed_data_provider)}; |
203 | 0 | }, |
204 | 0 | [&] { |
205 | 0 | tx_destination = ScriptHash{ConsumeUInt160(fuzzed_data_provider)}; |
206 | 0 | }, |
207 | 0 | [&] { |
208 | 0 | tx_destination = WitnessV0ScriptHash{ConsumeUInt256(fuzzed_data_provider)}; |
209 | 0 | }, |
210 | 0 | [&] { |
211 | 0 | tx_destination = WitnessV0KeyHash{ConsumeUInt160(fuzzed_data_provider)}; |
212 | 0 | }, |
213 | 0 | [&] { |
214 | 0 | tx_destination = WitnessV1Taproot{XOnlyPubKey{ConsumeUInt256(fuzzed_data_provider)}}; |
215 | 0 | }, |
216 | 0 | [&] { |
217 | 0 | tx_destination = PayToAnchor{}; |
218 | 0 | }, |
219 | 0 | [&] { |
220 | 0 | std::vector<unsigned char> program{ConsumeRandomLengthByteVector(fuzzed_data_provider, /*max_length=*/40)}; |
221 | 0 | if (program.size() < 2) { |
222 | 0 | program = {0, 0}; |
223 | 0 | } |
224 | 0 | tx_destination = WitnessUnknown{fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(2, 16), program}; |
225 | 0 | })}; |
226 | 0 | Assert(call_size == std::variant_size_v<CTxDestination>); |
227 | 0 | return tx_destination; |
228 | 0 | } |
229 | | |
230 | | CKey ConsumePrivateKey(FuzzedDataProvider& fuzzed_data_provider, std::optional<bool> compressed) noexcept |
231 | 0 | { |
232 | 0 | auto key_data = fuzzed_data_provider.ConsumeBytes<uint8_t>(32); |
233 | 0 | key_data.resize(32); |
234 | 0 | CKey key; |
235 | 0 | bool compressed_value = compressed ? *compressed : fuzzed_data_provider.ConsumeBool(); |
236 | 0 | key.Set(key_data.begin(), key_data.end(), compressed_value); |
237 | 0 | return key; |
238 | 0 | } |
239 | | |
240 | | bool ContainsSpentInput(const CTransaction& tx, const CCoinsViewCache& inputs) noexcept |
241 | 0 | { |
242 | 0 | for (const CTxIn& tx_in : tx.vin) { |
243 | 0 | const Coin& coin = inputs.AccessCoin(tx_in.prevout); |
244 | 0 | if (coin.IsSpent()) { |
245 | 0 | return true; |
246 | 0 | } |
247 | 0 | } |
248 | 0 | return false; |
249 | 0 | } |
250 | | |
251 | | FILE* FuzzedFileProvider::open() |
252 | 0 | { |
253 | 0 | SetFuzzedErrNo(m_fuzzed_data_provider); |
254 | 0 | if (m_fuzzed_data_provider.ConsumeBool()) { |
255 | 0 | return nullptr; |
256 | 0 | } |
257 | 0 | std::string mode; |
258 | 0 | CallOneOf( |
259 | 0 | m_fuzzed_data_provider, |
260 | 0 | [&] { |
261 | 0 | mode = "r"; |
262 | 0 | }, |
263 | 0 | [&] { |
264 | 0 | mode = "r+"; |
265 | 0 | }, |
266 | 0 | [&] { |
267 | 0 | mode = "w"; |
268 | 0 | }, |
269 | 0 | [&] { |
270 | 0 | mode = "w+"; |
271 | 0 | }, |
272 | 0 | [&] { |
273 | 0 | mode = "a"; |
274 | 0 | }, |
275 | 0 | [&] { |
276 | 0 | mode = "a+"; |
277 | 0 | }); |
278 | 0 | #if defined _GNU_SOURCE && (defined(__linux__) || defined(__FreeBSD__)) |
279 | 0 | const cookie_io_functions_t io_hooks = { |
280 | 0 | FuzzedFileProvider::read, |
281 | 0 | FuzzedFileProvider::write, |
282 | 0 | FuzzedFileProvider::seek, |
283 | 0 | FuzzedFileProvider::close, |
284 | 0 | }; |
285 | 0 | return fopencookie(this, mode.c_str(), io_hooks); |
286 | | #else |
287 | | (void)mode; |
288 | | return nullptr; |
289 | | #endif |
290 | 0 | } |
291 | | |
292 | | ssize_t FuzzedFileProvider::read(void* cookie, char* buf, size_t size) |
293 | 0 | { |
294 | 0 | FuzzedFileProvider* fuzzed_file = (FuzzedFileProvider*)cookie; |
295 | 0 | SetFuzzedErrNo(fuzzed_file->m_fuzzed_data_provider); |
296 | 0 | if (buf == nullptr || size == 0 || fuzzed_file->m_fuzzed_data_provider.ConsumeBool()) { |
297 | 0 | return fuzzed_file->m_fuzzed_data_provider.ConsumeBool() ? 0 : -1; |
298 | 0 | } |
299 | 0 | const std::vector<uint8_t> random_bytes = fuzzed_file->m_fuzzed_data_provider.ConsumeBytes<uint8_t>(size); |
300 | 0 | if (random_bytes.empty()) { |
301 | 0 | return 0; |
302 | 0 | } |
303 | 0 | std::memcpy(buf, random_bytes.data(), random_bytes.size()); |
304 | 0 | if (AdditionOverflow(fuzzed_file->m_offset, (int64_t)random_bytes.size())) { |
305 | 0 | return fuzzed_file->m_fuzzed_data_provider.ConsumeBool() ? 0 : -1; |
306 | 0 | } |
307 | 0 | fuzzed_file->m_offset += random_bytes.size(); |
308 | 0 | return random_bytes.size(); |
309 | 0 | } |
310 | | |
311 | | ssize_t FuzzedFileProvider::write(void* cookie, const char* buf, size_t size) |
312 | 0 | { |
313 | 0 | FuzzedFileProvider* fuzzed_file = (FuzzedFileProvider*)cookie; |
314 | 0 | SetFuzzedErrNo(fuzzed_file->m_fuzzed_data_provider); |
315 | 0 | const ssize_t n = fuzzed_file->m_fuzzed_data_provider.ConsumeIntegralInRange<ssize_t>(0, size); |
316 | 0 | if (AdditionOverflow(fuzzed_file->m_offset, (int64_t)n)) { |
317 | 0 | return 0; |
318 | 0 | } |
319 | 0 | fuzzed_file->m_offset += n; |
320 | 0 | return n; |
321 | 0 | } |
322 | | |
323 | | int FuzzedFileProvider::seek(void* cookie, int64_t* offset, int whence) |
324 | 0 | { |
325 | 0 | assert(whence == SEEK_SET || whence == SEEK_CUR || whence == SEEK_END); |
326 | 0 | FuzzedFileProvider* fuzzed_file = (FuzzedFileProvider*)cookie; |
327 | 0 | SetFuzzedErrNo(fuzzed_file->m_fuzzed_data_provider); |
328 | 0 | int64_t new_offset = 0; |
329 | 0 | if (whence == SEEK_SET) { |
330 | 0 | new_offset = *offset; |
331 | 0 | } else if (whence == SEEK_CUR) { |
332 | 0 | if (AdditionOverflow(fuzzed_file->m_offset, *offset)) { |
333 | 0 | return -1; |
334 | 0 | } |
335 | 0 | new_offset = fuzzed_file->m_offset + *offset; |
336 | 0 | } else if (whence == SEEK_END) { |
337 | 0 | const int64_t n = fuzzed_file->m_fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, 4096); |
338 | 0 | if (AdditionOverflow(n, *offset)) { |
339 | 0 | return -1; |
340 | 0 | } |
341 | 0 | new_offset = n + *offset; |
342 | 0 | } |
343 | 0 | if (new_offset < 0) { |
344 | 0 | return -1; |
345 | 0 | } |
346 | 0 | fuzzed_file->m_offset = new_offset; |
347 | 0 | *offset = new_offset; |
348 | 0 | return fuzzed_file->m_fuzzed_data_provider.ConsumeIntegralInRange<int>(-1, 0); |
349 | 0 | } |
350 | | |
351 | | int FuzzedFileProvider::close(void* cookie) |
352 | 0 | { |
353 | 0 | FuzzedFileProvider* fuzzed_file = (FuzzedFileProvider*)cookie; |
354 | 0 | SetFuzzedErrNo(fuzzed_file->m_fuzzed_data_provider); |
355 | 0 | return fuzzed_file->m_fuzzed_data_provider.ConsumeIntegralInRange<int>(-1, 0); |
356 | 0 | } |