Use this skill whenever the user wants to build or compile a C or C++ library with LibFuzzer + sanitizer instrumentation as the prerequisite step before writing or running fuzz harnesses. Triggers on phrases like "build the library for fuzzing", "compile with sanitizer instrumentation", "instrument the library", "构建插桩版本", "为 fuzzing 编译库", "build_harness", "fuzzer-no-link", "asan build", "msan build", "为库插桩", or when the user asks how ASan and MSan differ in the context of fuzzing. Also trigger proactively when the user is setting up a fuzzing environment and the target library has not been built yet — this skill must run before libfuzzer-harness-writer or libfuzzer-oss-packager can proceed. Use this skill for any C/C++ library regardless of build system (CMake, autoconf, meson, make).
Build a C/C++ library with LibFuzzer + sanitizer instrumentation following the OSS-Fuzz model. This is Stage 0 of the fuzzing pipeline — it must complete before harnesses can be written or compiled.
The library and the harness are compiled with different -fsanitize flags:
Library build: -fsanitize=fuzzer-no-link,<sanitizer>
↑
Inserts SanitizerCoverage (PC-table + inline counters)
and sanitizer checks into every function.
Does NOT link the fuzzer engine — the library has no main().
Harness build: -fsanitize=fuzzer,<sanitizer>
↑
Compiles LLVMFuzzerTestOneInput + links LibFuzzer engine.
Links against the instrumented library above.
This separation means: compile the library once, link many harnesses against it.
ASan and MSan are — they cannot be combined. Build a separate binary for each sanitizer you want to use.
| Sanitizer | Flag | Detects | Key constraints |
|---|---|---|---|
| ASan | address | heap/stack overflow, UAF, double-free, leaks | None |
| MSan | memory | reads of uninitialized memory (ASan cannot!) | Must disable inline ASM; needs MSan-instrumented libc++ |
| UBSan | undefined | signed overflow, null deref, bad casts | Often combined with ASan in practice |
| Coverage only | (omit sanitizer) | — | Useful for throughput benchmarking |
Standard OSS-Fuzz practice: build asan and msan separately, run both.
The build_harness/ directory must live inside the target library's source
root, not in any parent or project-level directory. This keeps the build
artifacts co-located with the source they were built from and avoids
cross-library confusion.
<lib_root>/ ← the library's own source tree (e.g. target_project/aom/)
├── CMakeLists.txt
├── ...
└── build_harness/ ← always here, never in a parent directory
├── build_<name>.sh
├── asan/
│ ├── lib<name>.a ← instrumented static archive (for harness linking)
│ ├── lib<name>.so ← instrumented shared library (for dynamic linking)
│ ├── cmake_build/ ← (or autoconf_build/, etc.)
│ └── include/ ← public headers + generated config headers
└── msan/
├── lib<name>.a
├── lib<name>.so
├── cmake_build/
└── include/
.a and .so?.a — standard for LibFuzzer harnesses: links everything into one binary,
no runtime path issues, easiest to distribute..so — needed when the harness or test driver loads the library dynamically
(dlopen), or when downstream consumers expect a shared object. Also required
by some OSS-Fuzz build scripts that use $LIB_FUZZING_ENGINE with -shared.Always compile with -fPIC so the same object files satisfy both formats.
Before writing anything, collect:
Build system — look for CMakeLists.txt, configure.ac / configure,
meson.build, or a plain Makefile. Read references/build_systems.md
for the exact commands per build system.
OSS-Fuzz reference script — if an oss-fuzz/projects/<libname>/build.sh
exists in the workspace, read it first. It encodes the authoritative
cmake flags, disabled features, and memory limits for that library.
MSan-specific cmake flags — search the library's CMakeLists.txt for
options that disable assembly (e.g., DAOM_TARGET_CPU, DENABLE_ASM,
DWITH_SIMD). These must be set for MSan builds.
Library-specific allocation limits — some libraries cap per-allocation
size (e.g., libaom's DAOM_MAX_ALLOCABLE_MEMORY). Check the OSS-Fuzz
script or search the source for such defines.
Write <lib_root>/build_harness/build_<libname>.sh (inside the library's own
source tree) using the template in scripts/build_template.sh as a starting
point. Because the script lives one level below <lib_root>, the template's
LIB_SRC="$(dirname "${SCRIPT_DIR}")" already resolves correctly — do not
append the library name manually.
-fPIC is always required — it makes object files position-independent so
they can be linked into both .a (static harness) and .so (shared library).
case "${SANITIZER}" in
asan)
SAN_FLAGS="-fsanitize=fuzzer-no-link,address -fPIC"
HARNESS_SAN_FLAGS="-fsanitize=fuzzer,address"
;;
msan)
# -fsanitize-memory-track-origins: when MSan fires, prints the allocation
# site of the uninitialized memory — essential for root-cause analysis.
SAN_FLAGS="-fsanitize=fuzzer-no-link,memory -fsanitize-memory-track-origins -fPIC"
HARNESS_SAN_FLAGS="-fsanitize=fuzzer,memory -fsanitize-memory-track-origins"
# MUST disable inline ASM for MSan (see references/build_systems.md)
;;
ubsan)
SAN_FLAGS="-fsanitize=fuzzer-no-link,undefined -fPIC"
HARNESS_SAN_FLAGS="-fsanitize=fuzzer,undefined"
;;
none)
SAN_FLAGS="-fsanitize=fuzzer-no-link -fPIC"
HARNESS_SAN_FLAGS="-fsanitize=fuzzer"
;;
esac
See references/build_systems.md for the exact incantation per build system.
The short version:
-DCMAKE_C_FLAGS="$SAN_FLAGS" OR library-specific -DSANITIZE=CC=clang CFLAGS="$SAN_FLAGS" ./configure ...CC=clang CFLAGS="$SAN_FLAGS" meson setup build/MSan requires that every byte of shadow memory be initialized by all code
paths, including the C++ standard library. OSS-Fuzz provides a pre-built
MSan-instrumented libc++ at /usr/local/lib/libcxx_msan/. If that path is
absent (local environment), add this warning to the script and document that
false positives from stdlib calls are expected:
if [ ! -d /usr/local/lib/libcxx_msan ]; then
echo "WARNING: No MSan-instrumented libc++ found."
echo " False positives from stdlib are expected."
echo " For clean MSan: use the OSS-Fuzz Docker container."
fi
Run from inside the library's source root (where build_harness/ lives):
cd <lib_root>
CC=clang CXX=clang++ bash build_harness/build_<libname>.sh asan
CC=clang CXX=clang++ bash build_harness/build_<libname>.sh msan
If the build fails, common causes:
-D<OPTION>=generic or =0rm -rf cmake_build/ firstyasm, nasm) → install or disable ASM as aboveRun these checks for each build — both asan and msan must pass for both
.a and .so:
# ── ASan build ────────────────────────────────────────────────────────────────
LIB_A=build_harness/asan/lib<name>.a
LIB_SO=build_harness/asan/lib<name>.so
nm $LIB_A | grep -c '__sanitizer_cov' || echo "FAIL .a: no SanitizerCoverage"
nm $LIB_A | grep -c '__asan' || echo "FAIL .a: no ASan symbols"
nm $LIB_A | grep 'LLVMFuzzerTestOneInput' && echo "FAIL .a: library contains fuzzer main"
nm $LIB_SO | grep -c '__sanitizer_cov' || echo "FAIL .so: no SanitizerCoverage"
nm $LIB_SO | grep -c '__asan' || echo "FAIL .so: no ASan symbols"
# ── MSan build ────────────────────────────────────────────────────────────────
LIB_A=build_harness/msan/lib<name>.a
LIB_SO=build_harness/msan/lib<name>.so
nm $LIB_A | grep -c '__sanitizer_cov' || echo "FAIL .a: no SanitizerCoverage"
nm $LIB_A | grep -c '__msan' || echo "FAIL .a: no MSan symbols"
nm $LIB_A | grep 'LLVMFuzzerTestOneInput' && echo "FAIL .a: library contains fuzzer main"
nm $LIB_SO | grep -c '__sanitizer_cov' || echo "FAIL .so: no SanitizerCoverage"
nm $LIB_SO | grep -c '__msan' || echo "FAIL .so: no MSan symbols"
All checks must pass before proceeding to harness compilation.
At the end of the build script, always print the exact commands for compiling
a harness against both builds using both .a and .so, including the
correct include paths:
# ASan harness — static linking (.a)
$CXX -std=c++17 -fsanitize=fuzzer,address \
-I build_harness/asan/include \
your_fuzzer.cc build_harness/asan/lib<name>.a -o your_fuzzer_asan
# ASan harness — dynamic linking (.so)
$CXX -std=c++17 -fsanitize=fuzzer,address \
-I build_harness/asan/include \
your_fuzzer.cc -L build_harness/asan -l<name> \
-Wl,-rpath,build_harness/asan -o your_fuzzer_asan_dyn
# MSan harness — static linking (.a)
$CXX -std=c++17 -fsanitize=fuzzer,memory -fsanitize-memory-track-origins \
-I build_harness/msan/include \
your_fuzzer.cc build_harness/msan/lib<name>.a -o your_fuzzer_msan
# MSan harness — dynamic linking (.so)
$CXX -std=c++17 -fsanitize=fuzzer,memory -fsanitize-memory-track-origins \
-I build_harness/msan/include \
your_fuzzer.cc -L build_harness/msan -l<name> \
-Wl,-rpath,build_harness/msan -o your_fuzzer_msan_dyn
This makes it easy to hand off to the harness writing step.
scripts/build_template.sh — copy and adapt for each libraryreferences/build_systems.md — per-build-system flag reference (CMake,
autoconf, meson, plain make) with MSan-specific ASM disable patterns