DEV Community

stringintech
stringintech

Posted on • Originally published at stringin.tech

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:

brew install afl++
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

Troubleshooting LLVM Issues

However, you may run into the same issue I did after attempting to build with cmake --build build_fuzz:

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
Enter fullscreen mode Exit fullscreen mode

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:

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"
Enter fullscreen mode Exit fullscreen mode

Replace <YOUR_VERSION> with your installed LLVM version (e.g., 21.1.2).

Now build:

cmake --build build_fuzz
Enter fullscreen mode Exit fullscreen mode

Running the Fuzzer

To run a specific fuzz target (I'm running the cmpctblock target introduced in PR#33300):

Create input and output directories:

mkdir -p fuzz-inputs/ fuzz-outputs/
Enter fullscreen mode Exit fullscreen mode

Generate initial test input:

head -c 1000 /dev/urandom > fuzz-inputs/input.dat
Enter fullscreen mode Exit fullscreen mode

Configure system for AFL++ (may require sudo):

sudo afl-system-config
Enter fullscreen mode Exit fullscreen mode

Start fuzzing:

FUZZ=cmpctblock afl-fuzz -i fuzz-inputs -o fuzz-outputs -- build_fuzz/bin/fuzz
Enter fullscreen mode Exit fullscreen mode

You should see AFL++ running successfully:

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 :-) ──┘
Enter fullscreen mode Exit fullscreen mode

Using AFL_DEBUG and AFL_NO_UI environment variables provides debug logs in a more readable format for troubleshooting:

FUZZ=cmpctblock AFL_DEBUG=1 AFL_NO_UI=1 afl-fuzz -i fuzz-inputs -o fuzz-outputs -- build_fuzz/bin/fuzz
Enter fullscreen mode Exit fullscreen mode
[+] 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...
Enter fullscreen mode Exit fullscreen mode

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:

FUZZ=cmpctblock AFL_NO_FORKSRV=1 afl-fuzz -i fuzz-inputs -o fuzz-outputs -- build_fuzz/bin/fuzz
Enter fullscreen mode Exit fullscreen mode

Top comments (0)