fuzzing.md
1 # Fuzzing Bitcoin Core using libFuzzer 2 3 ## Quickstart guide 4 5 To quickly get started fuzzing Bitcoin Core using [libFuzzer](https://llvm.org/docs/LibFuzzer.html): 6 7 ```sh 8 $ git clone https://github.com/bitcoin/bitcoin 9 $ cd bitcoin/ 10 $ ./autogen.sh 11 $ CC=clang CXX=clang++ ./configure --enable-fuzz --with-sanitizers=address,fuzzer,undefined 12 # macOS users: If you have problem with this step then make sure to read "macOS hints for 13 # libFuzzer" on https://github.com/bitcoin/bitcoin/blob/master/doc/fuzzing.md#macos-hints-for-libfuzzer 14 $ make 15 $ FUZZ=process_message src/test/fuzz/fuzz 16 # abort fuzzing using ctrl-c 17 ``` 18 19 There is also a runner script to execute all fuzz targets. Refer to 20 `./test/fuzz/test_runner.py --help` for more details. 21 22 ## Overview of Bitcoin Core fuzzing 23 24 [Google](https://github.com/google/fuzzing/) has a good overview of fuzzing in general, with contributions from key architects of some of the most-used fuzzers. [This paper](https://agroce.github.io/bitcoin_report.pdf) includes an external overview of the status of Bitcoin Core fuzzing, as of summer 2021. [John Regehr](https://blog.regehr.org/archives/1687) provides good advice on writing code that assists fuzzers in finding bugs, which is useful for developers to keep in mind. 25 26 ## Fuzzing harnesses and output 27 28 [`process_message`](https://github.com/bitcoin/bitcoin/blob/master/src/test/fuzz/process_message.cpp) is a fuzzing harness for the [`ProcessMessage(...)` function (`net_processing`)](https://github.com/bitcoin/bitcoin/blob/master/src/net_processing.cpp). The available fuzzing harnesses are found in [`src/test/fuzz/`](https://github.com/bitcoin/bitcoin/tree/master/src/test/fuzz). 29 30 The fuzzer will output `NEW` every time it has created a test input that covers new areas of the code under test. For more information on how to interpret the fuzzer output, see the [libFuzzer documentation](https://llvm.org/docs/LibFuzzer.html). 31 32 If you specify a corpus directory then any new coverage increasing inputs will be saved there: 33 34 ```sh 35 $ mkdir -p process_message-seeded-from-thin-air/ 36 $ FUZZ=process_message src/test/fuzz/fuzz process_message-seeded-from-thin-air/ 37 INFO: Seed: 840522292 38 INFO: Loaded 1 modules (424174 inline 8-bit counters): 424174 [0x55e121ef9ab8, 0x55e121f613a6), 39 INFO: Loaded 1 PC tables (424174 PCs): 424174 [0x55e121f613a8,0x55e1225da288), 40 INFO: 0 files found in process_message-seeded-from-thin-air/ 41 INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes 42 INFO: A corpus is not provided, starting from an empty corpus 43 #2 INITED cov: 94 ft: 95 corp: 1/1b exec/s: 0 rss: 150Mb 44 #3 NEW cov: 95 ft: 96 corp: 2/3b lim: 4 exec/s: 0 rss: 150Mb L: 2/2 MS: 1 InsertByte- 45 #4 NEW cov: 96 ft: 98 corp: 3/7b lim: 4 exec/s: 0 rss: 150Mb L: 4/4 MS: 1 CrossOver- 46 #21 NEW cov: 96 ft: 100 corp: 4/11b lim: 4 exec/s: 0 rss: 150Mb L: 4/4 MS: 2 ChangeBit-CrossOver- 47 #324 NEW cov: 101 ft: 105 corp: 5/12b lim: 6 exec/s: 0 rss: 150Mb L: 6/6 MS: 5 CrossOver-ChangeBit-CopyPart-ChangeBit-ChangeBinInt- 48 #1239 REDUCE cov: 102 ft: 106 corp: 6/24b lim: 14 exec/s: 0 rss: 150Mb L: 13/13 MS: 5 ChangeBit-CrossOver-EraseBytes-ChangeBit-InsertRepeatedBytes- 49 #1272 REDUCE cov: 102 ft: 106 corp: 6/23b lim: 14 exec/s: 0 rss: 150Mb L: 12/12 MS: 3 ChangeBinInt-ChangeBit-EraseBytes- 50 NEW_FUNC[1/677]: 0x55e11f456690 in std::_Function_base::~_Function_base() /usr/lib/gcc/x86_64-linux-gnu/8/../../../../include/c++/8/bits/std_function.h:255 51 NEW_FUNC[2/677]: 0x55e11f465800 in CDataStream::CDataStream(std::vector<unsigned char, std::allocator<unsigned char> > const&, int, int) src/./streams.h:248 52 #2125 REDUCE cov: 4820 ft: 4867 corp: 7/29b lim: 21 exec/s: 0 rss: 155Mb L: 6/12 MS: 2 CopyPart-CMP- DE: "block"- 53 NEW_FUNC[1/9]: 0x55e11f64d790 in std::_Rb_tree<uint256, std::pair<uint256 const, std::chrono::duration<long, std::ratio<1l, 1000000l> > >, std::_Select1st<std::pair<uint256 const, std::chrono::duration<long, std::ratio<1l, 1000000l> > > >, std::less<uint256>, std::allocator<std::pair<uint256 const, std::chrono::duration<long, std::ratio<1l, 1000000l> > > > >::~_Rb_tree() /usr/lib/gcc/x86_64-linux-gnu/8/../../../../include/c++/8/bits/stl_tree.h:972 54 NEW_FUNC[2/9]: 0x55e11f64d870 in std::_Rb_tree<uint256, std::pair<uint256 const, std::chrono::duration<long, std::ratio<1l, 1000000l> > >, std::_Select1st<std::pair<uint256 const, std::chrono::duration<long, std::ratio<1l, 1000000l> > > >, std::less<uint256>, std::allocator<std::pair<uint256 const, std::chrono::duration<long, std::ratio<1l, 1000000l> > > > >::_M_erase(std::_Rb_tree_node<std::pair<uint256 const, std::chrono::duration<long, std::ratio<1l, 1000000l> > > >*) /usr/lib/gcc/x86_64-linux-gnu/8/../../../../include/c++/8/bits/stl_tree.h:1875 55 #2228 NEW cov: 4898 ft: 4971 corp: 8/35b lim: 21 exec/s: 0 rss: 156Mb L: 6/12 MS: 3 EraseBytes-CopyPart-PersAutoDict- DE: "block"- 56 NEW_FUNC[1/5]: 0x55e11f46df70 in std::enable_if<__and_<std::allocator_traits<zero_after_free_allocator<char> >::__construct_helper<char, unsigned char const&>::type>::value, void>::type std::allocator_traits<zero_after_free_allocator<char> >::_S_construct<char, unsigned char const&>(zero_after_free_allocator<char>&, char*, unsigned char const&) /usr/lib/gcc/x86_64-linux-gnu/8/../../../../include/c++/8/bits/alloc_traits.h:243 57 NEW_FUNC[2/5]: 0x55e11f477390 in std::vector<unsigned char, std::allocator<unsigned char> >::data() /usr/lib/gcc/x86_64-linux-gnu/8/../../../../include/c++/8/bits/stl_vector.h:1056 58 #2456 NEW cov: 4933 ft: 5042 corp: 9/55b lim: 21 exec/s: 0 rss: 160Mb L: 20/20 MS: 3 ChangeByte-InsertRepeatedBytes-PersAutoDict- DE: "block"- 59 #2467 NEW cov: 4933 ft: 5043 corp: 10/76b lim: 21 exec/s: 0 rss: 161Mb L: 21/21 MS: 1 InsertByte- 60 #4215 NEW cov: 4941 ft: 5129 corp: 17/205b lim: 29 exec/s: 4215 rss: 350Mb L: 29/29 MS: 5 InsertByte-ChangeBit-CopyPart-InsertRepeatedBytes-CrossOver- 61 #4567 REDUCE cov: 4941 ft: 5129 corp: 17/204b lim: 29 exec/s: 4567 rss: 404Mb L: 24/29 MS: 2 ChangeByte-EraseBytes- 62 #6642 NEW cov: 4941 ft: 5138 corp: 18/244b lim: 43 exec/s: 2214 rss: 450Mb L: 43/43 MS: 3 CopyPart-CMP-CrossOver- DE: "verack"- 63 # abort fuzzing using ctrl-c 64 $ ls process_message-seeded-from-thin-air/ 65 349ac589fc66a09abc0b72bb4ae445a7a19e2cd8 4df479f1f421f2ea64b383cd4919a272604087a7 66 a640312c98dcc55d6744730c33e41c5168c55f09 b135de16e4709558c0797c15f86046d31c5d86d7 67 c000f7b41b05139de8b63f4cbf7d1ad4c6e2aa7f fc52cc00ec1eb1c08470e69f809ae4993fa70082 68 $ cat --show-nonprinting process_message-seeded-from-thin-air/349ac589fc66a09abc0b72bb4ae445a7a19e2cd8 69 block^@M-^?M-^?M-^?M-^?M-^?nM-^?M-^? 70 ``` 71 72 In this case the fuzzer managed to create a `block` message which when passed to `ProcessMessage(...)` increased coverage. 73 74 It is possible to specify `bitcoind` arguments to the `fuzz` executable. 75 Depending on the test, they may be ignored or consumed and alter the behavior 76 of the test. Just make sure to use double-dash to distinguish them from the 77 fuzzer's own arguments: 78 79 ```sh 80 $ FUZZ=address_deserialize_v2 src/test/fuzz/fuzz -runs=1 fuzz_seed_corpus/address_deserialize_v2 --checkaddrman=5 --printtoconsole=1 81 ``` 82 83 ## Fuzzing corpora 84 85 The project's collection of seed corpora is found in the [`bitcoin-core/qa-assets`](https://github.com/bitcoin-core/qa-assets) repo. 86 87 To fuzz `process_message` using the [`bitcoin-core/qa-assets`](https://github.com/bitcoin-core/qa-assets) seed corpus: 88 89 ```sh 90 $ git clone https://github.com/bitcoin-core/qa-assets 91 $ FUZZ=process_message src/test/fuzz/fuzz qa-assets/fuzz_seed_corpus/process_message/ 92 INFO: Seed: 1346407872 93 INFO: Loaded 1 modules (424174 inline 8-bit counters): 424174 [0x55d8a9004ab8, 0x55d8a906c3a6), 94 INFO: Loaded 1 PC tables (424174 PCs): 424174 [0x55d8a906c3a8,0x55d8a96e5288), 95 INFO: 991 files found in qa-assets/fuzz_seed_corpus/process_message/ 96 INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes 97 INFO: seed corpus: files: 991 min: 1b max: 1858b total: 288291b rss: 150Mb 98 #993 INITED cov: 7063 ft: 8236 corp: 25/3821b exec/s: 0 rss: 181Mb 99 … 100 ``` 101 102 ## Run without sanitizers for increased throughput 103 104 Fuzzing on a harness compiled with `--with-sanitizers=address,fuzzer,undefined` is good for finding bugs. However, the very slow execution even under libFuzzer will limit the ability to find new coverage. A good approach is to perform occasional long runs without the additional bug-detectors (configure `--with-sanitizers=fuzzer`) and then merge new inputs into a corpus as described in the qa-assets repo (https://github.com/bitcoin-core/qa-assets/blob/main/.github/PULL_REQUEST_TEMPLATE.md). Patience is useful; even with improved throughput, libFuzzer may need days and 10s of millions of executions to reach deep/hard targets. 105 106 ## Reproduce a fuzzer crash reported by the CI 107 108 - `cd` into the `qa-assets` directory and update it with `git pull qa-assets` 109 - locate the crash case described in the CI output, e.g. `Test unit written to 110 ./crash-1bc91feec9fc00b107d97dc225a9f2cdaa078eb6` 111 - make sure to compile with all sanitizers, if they are needed (fuzzing runs 112 more slowly with sanitizers enabled, but a crash should be reproducible very 113 quickly from a crash case) 114 - run the fuzzer with the case number appended to the seed corpus path: 115 `FUZZ=process_message src/test/fuzz/fuzz 116 qa-assets/fuzz_seed_corpus/process_message/1bc91feec9fc00b107d97dc225a9f2cdaa078eb6` 117 118 ## Submit improved coverage 119 120 If you find coverage increasing inputs when fuzzing you are highly encouraged to submit them for inclusion in the [`bitcoin-core/qa-assets`](https://github.com/bitcoin-core/qa-assets) repo. 121 122 Every single pull request submitted against the Bitcoin Core repo is automatically tested against all inputs in the [`bitcoin-core/qa-assets`](https://github.com/bitcoin-core/qa-assets) repo. Contributing new coverage increasing inputs is an easy way to help make Bitcoin Core more robust. 123 124 ## macOS hints for libFuzzer 125 126 The default Clang/LLVM version supplied by Apple on macOS does not include 127 fuzzing libraries, so macOS users will need to install a full version, for 128 example using `brew install llvm`. 129 130 You may also need to take care of giving the correct path for `clang` and 131 `clang++`, like `CC=/path/to/clang CXX=/path/to/clang++` if the non-systems 132 `clang` does not come first in your path. 133 134 Full configure that was tested on macOS with `brew` installed `llvm`: 135 136 ```sh 137 ./configure --enable-fuzz --with-sanitizers=fuzzer,address,undefined CC=$(brew --prefix llvm)/bin/clang CXX=$(brew --prefix llvm)/bin/clang++ 138 ``` 139 140 Read the [libFuzzer documentation](https://llvm.org/docs/LibFuzzer.html) for more information. This [libFuzzer tutorial](https://github.com/google/fuzzing/blob/master/tutorial/libFuzzerTutorial.md) might also be of interest. 141 142 # Fuzzing Bitcoin Core using afl++ 143 144 ## Quickstart guide 145 146 To quickly get started fuzzing Bitcoin Core using [afl++](https://github.com/AFLplusplus/AFLplusplus): 147 148 ```sh 149 $ git clone https://github.com/bitcoin/bitcoin 150 $ cd bitcoin/ 151 $ git clone https://github.com/AFLplusplus/AFLplusplus 152 $ make -C AFLplusplus/ source-only 153 $ ./autogen.sh 154 # If afl-clang-lto is not available, see 155 # https://github.com/AFLplusplus/AFLplusplus#a-selecting-the-best-afl-compiler-for-instrumenting-the-target 156 $ CC=$(pwd)/AFLplusplus/afl-clang-lto CXX=$(pwd)/AFLplusplus/afl-clang-lto++ ./configure --enable-fuzz 157 $ make 158 # For macOS you may need to ignore x86 compilation checks when running "make". If so, 159 # try compiling using: AFL_NO_X86=1 make 160 $ mkdir -p inputs/ outputs/ 161 $ echo A > inputs/thin-air-input 162 $ FUZZ=bech32 AFLplusplus/afl-fuzz -i inputs/ -o outputs/ -- src/test/fuzz/fuzz 163 # You may have to change a few kernel parameters to test optimally - afl-fuzz 164 # will print an error and suggestion if so. 165 ``` 166 167 Read the [afl++ documentation](https://github.com/AFLplusplus/AFLplusplus) for more information. 168 169 # Fuzzing Bitcoin Core using Honggfuzz 170 171 ## Quickstart guide 172 173 To quickly get started fuzzing Bitcoin Core using [Honggfuzz](https://github.com/google/honggfuzz): 174 175 ```sh 176 $ git clone https://github.com/bitcoin/bitcoin 177 $ cd bitcoin/ 178 $ ./autogen.sh 179 $ git clone https://github.com/google/honggfuzz 180 $ cd honggfuzz/ 181 $ make 182 $ cd .. 183 $ CC=$(pwd)/honggfuzz/hfuzz_cc/hfuzz-clang CXX=$(pwd)/honggfuzz/hfuzz_cc/hfuzz-clang++ ./configure --enable-fuzz --with-sanitizers=address,undefined 184 $ make 185 $ mkdir -p inputs/ 186 $ FUZZ=process_message honggfuzz/honggfuzz -i inputs/ -- src/test/fuzz/fuzz 187 ``` 188 189 Read the [Honggfuzz documentation](https://github.com/google/honggfuzz/blob/master/docs/USAGE.md) for more information. 190 191 ## Fuzzing the Bitcoin Core P2P layer using Honggfuzz NetDriver 192 193 Honggfuzz NetDriver allows for very easy fuzzing of TCP servers such as Bitcoin 194 Core without having to write any custom fuzzing harness. The `bitcoind` server 195 process is largely fuzzed without modification. 196 197 This makes the fuzzing highly realistic: a bug reachable by the fuzzer is likely 198 also remotely triggerable by an untrusted peer. 199 200 To quickly get started fuzzing the P2P layer using Honggfuzz NetDriver: 201 202 ```sh 203 $ mkdir bitcoin-honggfuzz-p2p/ 204 $ cd bitcoin-honggfuzz-p2p/ 205 $ git clone https://github.com/bitcoin/bitcoin 206 $ cd bitcoin/ 207 $ ./autogen.sh 208 $ git clone https://github.com/google/honggfuzz 209 $ cd honggfuzz/ 210 $ make 211 $ cd .. 212 $ CC=$(pwd)/honggfuzz/hfuzz_cc/hfuzz-clang \ 213 CXX=$(pwd)/honggfuzz/hfuzz_cc/hfuzz-clang++ \ 214 ./configure --disable-wallet --with-gui=no \ 215 --with-sanitizers=address,undefined 216 $ git apply << "EOF" 217 diff --git a/src/compat/compat.h b/src/compat/compat.h 218 index 8195bceaec..cce2b31ff0 100644 219 --- a/src/compat/compat.h 220 +++ b/src/compat/compat.h 221 @@ -90,8 +90,12 @@ typedef char* sockopt_arg_type; 222 // building with a binutils < 2.36 is subject to this ld bug. 223 #define MAIN_FUNCTION __declspec(dllexport) int main(int argc, char* argv[]) 224 #else 225 +#ifdef HFND_FUZZING_ENTRY_FUNCTION_CXX 226 +#define MAIN_FUNCTION HFND_FUZZING_ENTRY_FUNCTION_CXX(int argc, char* argv[]) 227 +#else 228 #define MAIN_FUNCTION int main(int argc, char* argv[]) 229 #endif 230 +#endif 231 232 // Note these both should work with the current usage of poll, but best to be safe 233 // WIN32 poll is broken https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/ 234 diff --git a/src/net.cpp b/src/net.cpp 235 index 7601a6ea84..702d0f56ce 100644 236 --- a/src/net.cpp 237 +++ b/src/net.cpp 238 @@ -727,7 +727,7 @@ int V1TransportDeserializer::readHeader(Span<const uint8_t> msg_bytes) 239 } 240 241 // Check start string, network magic 242 - if (memcmp(hdr.pchMessageStart, m_chain_params.MessageStart(), CMessageHeader::MESSAGE_START_SIZE) != 0) { 243 + if (false && memcmp(hdr.pchMessageStart, m_chain_params.MessageStart(), CMessageHeader::MESSAGE_START_SIZE) != 0) { // skip network magic checking 244 LogPrint(BCLog::NET, "Header error: Wrong MessageStart %s received, peer=%d\n", HexStr(hdr.pchMessageStart), m_node_id); 245 return -1; 246 } 247 @@ -788,7 +788,7 @@ CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds 248 RandAddEvent(ReadLE32(hash.begin())); 249 250 // Check checksum and header message type string 251 - if (memcmp(hash.begin(), hdr.pchChecksum, CMessageHeader::CHECKSUM_SIZE) != 0) { 252 + if (false && memcmp(hash.begin(), hdr.pchChecksum, CMessageHeader::CHECKSUM_SIZE) != 0) { // skip checksum checking 253 LogPrint(BCLog::NET, "Header error: Wrong checksum (%s, %u bytes), expected %s was %s, peer=%d\n", 254 SanitizeString(msg.m_type), msg.m_message_size, 255 HexStr(Span{hash}.first(CMessageHeader::CHECKSUM_SIZE)), 256 EOF 257 $ make -C src/ bitcoind 258 $ mkdir -p inputs/ 259 $ honggfuzz/honggfuzz --exit_upon_crash --quiet --timeout 4 -n 1 -Q \ 260 -E HFND_TCP_PORT=18444 -f inputs/ -- \ 261 src/bitcoind -regtest -discover=0 -dns=0 -dnsseed=0 -listenonion=0 \ 262 -nodebuglogfile -bind=127.0.0.1:18444 -logthreadnames \ 263 -debug 264 ``` 265 266 # Fuzzing Bitcoin Core using Eclipser (v1.x) 267 268 ## Quickstart guide 269 270 To quickly get started fuzzing Bitcoin Core using [Eclipser v1.x](https://github.com/SoftSec-KAIST/Eclipser/tree/v1.x): 271 272 ```sh 273 $ git clone https://github.com/bitcoin/bitcoin 274 $ cd bitcoin/ 275 $ sudo vim /etc/apt/sources.list # Uncomment the lines starting with 'deb-src'. 276 $ sudo apt-get update 277 $ sudo apt-get build-dep qemu 278 $ sudo apt-get install libtool libtool-bin wget automake autoconf bison gdb 279 ``` 280 281 At this point, you must install the .NET core. The process differs, depending on your Linux distribution. 282 See [this link](https://learn.microsoft.com/en-us/dotnet/core/install/linux) for details. 283 On Ubuntu 20.04, the following should work: 284 285 ```sh 286 $ wget -q https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb 287 $ sudo dpkg -i packages-microsoft-prod.deb 288 $ rm packages-microsoft-prod.deb 289 $ sudo apt-get update 290 $ sudo apt-get install -y dotnet-sdk-2.1 291 ``` 292 293 You will also want to make sure Python is installed as `python` for the Eclipser install to succeed. 294 295 ```sh 296 $ git clone https://github.com/SoftSec-KAIST/Eclipser.git 297 $ cd Eclipser 298 $ git checkout v1.x 299 $ make 300 $ cd .. 301 $ ./autogen.sh 302 $ ./configure --enable-fuzz 303 $ make 304 $ mkdir -p outputs/ 305 $ FUZZ=bech32 dotnet Eclipser/build/Eclipser.dll fuzz -p src/test/fuzz/fuzz -t 36000 -o outputs --src stdin 306 ``` 307 308 This will perform 10 hours of fuzzing. 309 310 To make further use of the inputs generated by Eclipser, you 311 must first decode them: 312 313 ```sh 314 $ dotnet Eclipser/build/Eclipser.dll decode -i outputs/testcase -o decoded_outputs 315 ``` 316 This will place raw inputs in the directory `decoded_outputs/decoded_stdins`. Crashes are in the `outputs/crashes` directory, and must 317 be decoded in the same way. 318 319 Fuzzing with Eclipser will likely be much more effective if using an existing corpus: 320 321 ```sh 322 $ git clone https://github.com/bitcoin-core/qa-assets 323 $ FUZZ=bech32 dotnet Eclipser/build/Eclipser.dll fuzz -p src/test/fuzz/fuzz -t 36000 -i qa-assets/fuzz_seed_corpus/bech32 outputs --src stdin 324 ``` 325 326 Note that fuzzing with Eclipser on certain targets (those that create 'full nodes', e.g. `process_message*`) will, 327 for now, slowly fill `/tmp/` with improperly cleaned-up files, which will cause spurious crashes. 328 See [this proposed patch](https://github.com/bitcoin/bitcoin/pull/22472) for more information. 329 330 Read the [Eclipser documentation for v1.x](https://github.com/SoftSec-KAIST/Eclipser/tree/v1.x) for more details on using Eclipser. 331 332 333 # OSS-Fuzz 334 335 Bitcoin Core participates in Google's [OSS-Fuzz](https://github.com/google/oss-fuzz/tree/master/projects/bitcoin-core) 336 program, which includes a dashboard of [publicly disclosed vulnerabilities](https://bugs.chromium.org/p/oss-fuzz/issues/list?q=bitcoin-core). 337 Generally, we try to disclose vulnerabilities as soon as possible after they 338 are fixed to give users the knowledge they need to be protected. However, 339 because Bitcoin is a live P2P network, and not just standalone local software, 340 we might not fully disclose every issue within Google's standard 341 [90-day disclosure window](https://google.github.io/oss-fuzz/getting-started/bug-disclosure-guidelines/) 342 if a partial or delayed disclosure is important to protect users or the 343 function of the network. 344 345 OSS-Fuzz also produces [a fuzzing coverage report](https://oss-fuzz.com/coverage-report/job/libfuzzer_asan_bitcoin-core/latest).