Steps to build and run a Bitcoin Core fuzz target on Apple Silicon using AFL++ (tested on M1 and M3):
Installation
First, install AFL++ via Homebrew:
This comes with the AFL++ instrumentation compiler afl-clang-fast++
(located in /opt/homebrew/opt/afl++/bin
) and LLVM from Homebrew (/opt/homebrew/Cellar/llvm
). Note that afl-clang-lto
is not available on macOS.
Note: afl-clang-fast++
is a wrapper around Homebrew’s LLVM clang compiler (/opt/homebrew/Cellar/llvm/<VERSION>/bin/clang++
). When you compile with it, it produces binaries with the required runtime instrumentation that enables the AFL++ fuzz engine to track code coverage and guide fuzzing.
Configuration
Configure the fuzz build using afl-clang-fast
(replacing afl-clang-lto
in the command mentioned in Bitcoin Core’s fuzzing docs):
1
2
3
4
| cmake -B build_fuzz \
-DCMAKE_C_COMPILER="/opt/homebrew/bin/afl-clang-fast" \
-DCMAKE_CXX_COMPILER="/opt/homebrew/bin/afl-clang-fast++" \
-DBUILD_FOR_FUZZING=ON
|
Troubleshooting LLVM Issues
However, you may run into the same issue I did after attempting to build with cmake --build build_fuzz
:
1
2
3
4
5
| Undefined symbols for architecture arm64:
"std::__1::__hash_memory(void const*, unsigned long)", referenced from:
Arena::Arena(void*, unsigned long, unsigned long) in libbitcoin_util.a[32](lockedpool.cpp.o)
...
ld: symbol(s) not found for architecture arm64
|
This is caused by a mismatch: afl-clang-fast++
compiles with Homebrew’s LLVM headers (which declare __hash_memory
), but the linker defaults to Apple’s older system libc++ (which lacks this symbol).
Fix by explicitly using Homebrew’s LLVM for both compilation and linking:
1
2
3
4
5
6
| cmake -B build_fuzz \
-DCMAKE_C_COMPILER="/opt/homebrew/bin/afl-clang-fast" \
-DCMAKE_CXX_COMPILER="/opt/homebrew/bin/afl-clang-fast++" \
-DBUILD_FOR_FUZZING=ON \
-DCMAKE_CXX_FLAGS="-stdlib=libc++ -I/opt/homebrew/Cellar/llvm/<YOUR_VERSION>/include/c++/v1" \
-DCMAKE_EXE_LINKER_FLAGS="-L/opt/homebrew/Cellar/llvm/<YOUR_VERSION>/lib/c++ -lc++ -lc++abi"
|
Replace <YOUR_VERSION>
with your installed LLVM version (e.g., 21.1.2).
Now build:
1
| cmake --build build_fuzz
|
Running the Fuzzer
To run a specific fuzz target (I’m running the cmpctblock
target introduced in PR#33300):
Create input and output directories:
1
| mkdir -p fuzz-inputs/ fuzz-outputs/
|
Generate initial test input:
1
| head -c 1000 /dev/urandom > fuzz-inputs/input.dat
|
Configure system for AFL++ (may require sudo):
Start fuzzing:
1
| FUZZ=cmpctblock afl-fuzz -i fuzz-inputs -o fuzz-outputs -- build_fuzz/bin/fuzz
|
You should see AFL++ running successfully:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| american fuzzy lop ++4.33c {default} (build_fuzz/bin/fuzz) [explore]
ββ process timing βββββββββββββββββββββββββββββββββββββ¬β overall results βββββ
β run time : 0 days, 0 hrs, 0 min, 20 sec β cycles done : 0 β
β last new find : none seen yet β corpus count : 1 β
βlast saved crash : none seen yet βsaved crashes : 0 β
β last saved hang : none seen yet β saved hangs : 0 β
ββ cycle progress ββββββββββββββββββββββ¬β map coverageβ΄βββββββββββββββββββββββ€
β now processing : 0.0 (0.0%) β map density : 1.45% / 1.47% β
β runs timed out : 0 (0.00%) β count coverage : 1.11 bits/tuple β
ββ stage progress ββββββββββββββββββββββΌβ findings in depth ββββββββββββββββββ€
β now trying : trim 4/4 β favored items : 1 (100.00%) β
β stage execs : 87/250 (34.80%) β new edges on : 1 (100.00%) β
β total execs : 332 β total crashes : 0 (0 saved) β
β exec speed : 10.16/sec (zzzz...) β total tmouts : 0 (0 saved) β
ββ fuzzing strategy yields βββββββββββββ΄ββββββββββββββ¬β item geometry ββββββββ€
β bit flips : 0/0, 0/0, 0/0 β levels : 1 β
β byte flips : 0/0, 0/0, 0/0 β pending : 1 β
β arithmetics : 0/0, 0/0, 0/0 β pend fav : 1 β
β known ints : 0/0, 0/0, 0/0 β own finds : 0 β
β dictionary : 0/0, 0/0, 0/0, 0/0 β imported : 0 β
βhavoc/splice : 0/0, 0/0 β stability : 99.08% β
βpy/custom/rq : unused, unused, unused, unused βββββββββββββββββββββββββ
β trim/eff : n/a, n/a β [cpu: 24%]
ββ strategy: explore ββββββββββ state: started :-) βββ
|
Using AFL_DEBUG
and AFL_NO_UI
environment variables provides debug logs in a more readable format for troubleshooting:
1
| FUZZ=cmpctblock AFL_DEBUG=1 AFL_NO_UI=1 afl-fuzz -i fuzz-inputs -o fuzz-outputs -- build_fuzz/bin/fuzz
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| [+] Enabled environment variable AFL_DEBUG with value 1
[+] Enabled environment variable AFL_DEBUG with value 1
[+] Enabled environment variable AFL_NO_UI with value 1
afl-fuzz++4.33c based on afl by Michal Zalewski and a large online community
[+] AFL++ is maintained by Marc "van Hauser" Heuse, Dominik Maier, Andrea Fioraldi and Heiko "hexcoder" EiΓfeldt
[+] AFL++ is open source, get it at https://github.com/AFLplusplus/AFLplusplus
[+] NOTE: AFL++ >= v3 has changed defaults and behaviours - see README.md
[+] No -M/-S set, autoconfiguring for "-S default"
[*] Getting to work...
[+] Using exploration-based constant power schedule (EXPLORE)
[+] Enabled testcache with 50 MB
[+] Generating fuzz data with a length of min=1 max=1048576
[*] Checking CPU scaling governor...
[!] WARNING: Could not check CPU min frequency
[+] Disabling the UI because AFL_NO_UI is set.
[+] You have 8 CPU cores and 3 runnable tasks (utilization: 38%).
[+] Try parallel jobs - see /opt/homebrew/Cellar/afl++/4.33c_1/share/doc/afl/fuzzing_in_depth.md#c-using-multiple-cores
[*] Setting up output directories...
[+] Output directory exists but deemed OK to reuse.
[*] Deleting old session data...
[+] Output dir cleanup successful.
[*] Validating target binary...
[+] Persistent mode binary detected.
[*] Scanning 'fuzz-inputs'...
[*] Creating hard links for all input files...
[+] Loaded a total of 1 seeds.
[*] Spinning up the fork server...
|
Troubleshooting Fork Server Issues
With the fork server optimization enabled, you may face unexpected worker process terminations. I investigated the unexpected crashes caused by these terminations in the cmpctblock
fuzz harness and documented my findings in this GitHub comment.
To avoid such issues, disable the fork server optimization:
1
| FUZZ=cmpctblock AFL_NO_FORKSRV=1 afl-fuzz -i fuzz-inputs -o fuzz-outputs -- build_fuzz/bin/fuzz
|