Fuzzing Bitcoin Core using AFL++ on Apple Silicon

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:

1
brew install afl++

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):

1
sudo afl-system-config

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
Last updated on Sep 28, 2025 14:25 UTC
Built with Hugo
Theme Stack designed by Jimmy