Testland
Browse all skills & agents

sanitiser-integration-reference

Pure-reference catalog of compiler sanitisers used with fuzz testing - AddressSanitizer (ASan), UndefinedBehaviorSanitizer (UBSan), MemorySanitizer (MSan), ThreadSanitizer (TSan), and LeakSanitizer (LSan). Explains what each detects, compatibility (can ASan + UBSan combine? - yes; ASan + MSan? - no), build flags, runtime options (ASAN_OPTIONS / UBSAN_OPTIONS env vars), and the typical ~2x slowdown per ASan. Use to pick the right sanitiser per fuzz target, configure the build, and interpret crash reports.

Install with skills.sh (any agent)

npx skills add testland/qa --skill sanitiser-integration-reference
View source

sanitiser-integration-reference

Overview

Pure-reference catalog of the five clang sanitisers (ASan, UBSan, MSan, TSan, LSan) used with coverage-guided fuzz targets - what each detects, build flags, runtime options, compatibility matrix, performance overhead. Consumed by the per-language fuzzer skills and fuzz-target authoring. For corpus discipline see corpus-management-reference.

When to use

  • Choosing which sanitisers to enable for a fuzz target.
  • Interpreting a crash artefact's sanitiser report.
  • Tuning sanitiser runtime options for false positives / performance.
  • CI-gating builds with sanitisers enabled.

How to use

  1. Identify the fuzz target's language and threat model (memory safety, undefined behavior, uninitialised reads, or data races).
  2. Pick sanitisers from the summary table below; default to ASan + UBSan for most C / C++ targets.
  3. Check the compatibility matrix before combining - ASan + UBSan is fine, but ASan + MSan and anything + TSan are not, so split those into separate binaries.
  4. Build with -fsanitize=fuzzer,<sanitisers> plus -fno-sanitize-recover=all and -fno-omit-frame-pointer -g.
  5. Set runtime options (ASAN_OPTIONS, UBSAN_OPTIONS) so the fuzzer aborts on first error.
  6. When a crash lands, read the sanitiser report top-down: bug class, access, crash-site frame, then allocation / free site.
  7. For MSan-required libraries, build a separate MSan-only binary with all dependencies instrumented and run it as a second campaign.

The five sanitisers

SanitiserDetects (summary)Build flagSlowdown
ASanheap / stack / global OOB, use-after-free, double-free-fsanitize=address -fno-omit-frame-pointer -g~2x
UBSansigned overflow, div-by-zero, null deref, misaligned access-fsanitize=undefined -fno-sanitize-recover=all~10%
MSanuninitialised memory reads-fsanitize=memory -fno-omit-frame-pointer -fsanitize-memory-track-origins3x
TSandata races, deadlocks, thread-safety violations-fsanitize=thread -O1 -g5 - 15x
LSanmemory leaks at program exit-fsanitize=leak (or embedded in ASan)small

Full per-sanitiser detail - complete detect lists, the ASAN_OPTIONS / UBSAN_OPTIONS runtime-option tables, the MSan whole-program requirement, and LSan's embedded vs standalone modes: references/sanitiser-catalog.md.

Compatibility matrix

Can multiple sanitisers run in the same binary?

SanitiserASanUBSanMSanTSan
ASan-
UBSan-
MSan-
TSan-

The standard fuzzing pair is ASan + UBSan (catches most memory + UB issues, manageable slowdown):

clang -g -O1 -fsanitize=fuzzer,address,undefined \
      -fno-sanitize-recover=all \
      -fno-omit-frame-pointer fuzz_target.cc -o fuzz_target

For MSan-required projects (e.g., crypto libraries), build a separate MSan-only binary and run it as a second fuzzing campaign.

libFuzzer + sanitiser composition

The -fsanitize=fuzzer,address,undefined flag composes the libFuzzer engine with ASan + UBSan in one binary. Each sanitiser contributes its instrumentation.

Per llvm.org/docs/LibFuzzer.html (opens in new window):

# Build
clang -g -O1 \
  -fsanitize=fuzzer,address,undefined \
  -fno-sanitize-recover=all \
  fuzz_target.cc -o fuzz_target

# Run
ASAN_OPTIONS=abort_on_error=1:halt_on_error=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
  ./fuzz_target -max_total_time=3600 corpus/

Reading a sanitiser report

ASan output structure:

==1234==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7f...
READ of size 4 at 0x7f... thread T0
    #0 0x4015a3 in process_input src/parser.c:42:5
    #1 0x4012f0 in LLVMFuzzerTestOneInput fuzz_target.cc:10:3
    ...
0x7f... is located 0 bytes to the right of 16-byte region 0x7f..., 0x7f...)
allocated by thread T0 here:
    #0 0x40e7c0 in __interceptor_malloc
    #1 0x4015a3 in process_input src/parser.c:39:9

Key fields:

  • Bug class: heap-buffer-overflow, stack-use-after-return, use-after-free, double-free, memory-leak
  • Access: READ or WRITE, size
  • Stack: Top frame = crash site; below = call chain
  • Allocation site: Where the corrupted memory was allocated
  • Freed site (UAF): Where the memory was freed

Parse this for bug-report-from-failure to extract the failure assertion.

Per-language sanitiser support

LanguageASanUBSanMSanTSanLSan
C / C++ (clang / GCC)✓ (clang)
Rust (nightly)
Gopartial (race detector for TSan-equivalent)---
Swift-
Objective-C-

Java / Kotlin (Jazzer) uses JVM-level sanitisers (sanitisers for unsafe-API misuse, deserialisation gadgets, ReDoS) rather than clang's; see [jazzer-jvm-fuzzing.

Python (Atheris) uses per-module instrumentation + the host process's libFuzzer; you can attach ASan to the Python interpreter itself.

Worked example

A team fuzzes a C++ PNG parser. They choose ASan + UBSan (the standard pair) and build with:

clang -g -O1 -fsanitize=fuzzer,address,undefined \
      -fno-sanitize-recover=all -fno-omit-frame-pointer \
      png_fuzzer.cc -o png_fuzzer

Running under ASAN_OPTIONS=abort_on_error=1:halt_on_error=1, the fuzzer trips within minutes. The report opens with heap-buffer-overflow ... READ of size 4, top frame process_input src/parser.c:42, allocated at src/parser.c:39 (a 16-byte region). The bug class plus the allocation site pin it to an off-by-one in the chunk-length handling. The parser has no MSan dependency requirement, so they skip the separate MSan binary and hand the report to bug-report-from-failure.

Anti-patterns

Anti-patternWhy it failsFix
Fuzzing without sanitisersCatches only crashes; misses 80%+ of memory bugsAlways build with ASan + UBSan minimum
-fsanitize=address,memory togetherMSan + ASan incompatiblePick one; run separate binaries
MSan with non-MSan dependenciesFalse positives flood the reportBuild all dependencies with MSan or skip MSan
UBSan without -fno-sanitize-recover=allUBSan logs but doesn't abort; fuzzer never sees the bugAlways add -fno-sanitize-recover=all
ASan without -fno-omit-frame-pointerStack traces are uselessAlways add -fno-omit-frame-pointer -g
detect_leaks=0 in fuzz CILeak bugs go unnoticedDefault ASan settings (Linux LSan-enabled)
TSan + a non-thread-safe targetSlow + noisy; data races are everywherePick targets where thread-safety claims are made

Limitations

  • Performance trade-offs. ASan + UBSan ≈ 2-3x slowdown; TSan ≈ 10-15x. Long fuzz campaigns need budget planning.
  • Sanitiser incompatibility. No single binary catches everything; multiple campaigns required.
  • Heap-buffer-overflow on rdtsc edges. Some sanitisers have per-architecture false-positive surfaces.
  • Build system integration. Pre-existing build systems may need invasive changes (CMake -DCMAKE_C_FLAGS=-fsanitize=...).
  • Library compatibility. Some C / C++ libraries hand-roll memory tricks (custom allocators, intrusive lists) that sanitisers misflag.

References

Sanitiser catalog

Per-sanitiser detail for the five clang sanitisers used with coverage-guided fuzz targets - what each detects, build flags, runtime options, and performance overhead. Per clang.llvm.org sanitiser docs.

AddressSanitizer (ASan)

What it detects (per clang.llvm.org/docs/AddressSanitizer.html (opens in new window)):

  • Out-of-bounds accesses to heap, stack, and globals
  • Use-after-free
  • Double-free, invalid free
  • Memory leaks (experimental; LSan integrated)

Build flag: -fsanitize=address -fno-omit-frame-pointer -g

Performance: "Typical slowdown introduced by AddressSanitizer is 2x" per the docs.

Runtime options (ASAN_OPTIONS=key=value:...):

OptionEffect
detect_leaks=1Enable leak detection (default on Linux)
detect_stack_use_after_return=0Disable use-after-return checks (faster)
detect_container_overflow=0Disable container-overflow detection
symbolize=0Disable online symbolization (use post-mortem)
check_initialization_order=1Init-order checking
halt_on_error=1Stop on first error
abort_on_error=1SIGABRT on error (for fuzzers)

UndefinedBehaviorSanitizer (UBSan)

What it detects: signed integer overflow, division by zero, null pointer deref, misaligned access, float-int conversion overflow, invalid enum / bool, vptr corruption, function-pointer type mismatch, etc.

Build flag: -fsanitize=undefined -fno-sanitize-recover=all

The -fno-sanitize-recover=all is important for fuzzing - without it, UBSan logs but doesn't abort, so the fuzzer doesn't see the bug.

Performance: ~10% slowdown - much lighter than ASan.

Runtime options (UBSAN_OPTIONS):

  • print_stacktrace=1 - include stack trace in reports
  • halt_on_error=1 - abort on first error

MemorySanitizer (MSan)

What it detects: Uninitialised memory reads.

Build flag: -fsanitize=memory -fno-omit-frame-pointer -fsanitize-memory-track-origins

Performance: 3x slowdown.

Critical: MSan requires the entire program (and all dependencies) to be built with -fsanitize=memory. Linking against non-MSan-instrumented libraries produces false positives.

Compatibility: MSan is incompatible with ASan; cannot combine.

ThreadSanitizer (TSan)

What it detects: Data races, deadlocks, thread-safety violations.

Build flag: -fsanitize=thread -O1 -g

Performance: 5 - 15x slowdown + 5 - 10x memory.

Compatibility: TSan is incompatible with ASan and MSan.

LeakSanitizer (LSan)

What it detects: Memory leaks at program exit.

Modes:

  • Embedded in ASan: -fsanitize=address enables LSan by default on Linux. Toggle via detect_leaks=1.
  • Standalone: -fsanitize=leak - leak detection only, no other checks. Smaller overhead.

Related skills

afl-plus-plus

Author and run AFL++ - out-of-process coverage-guided fuzzer (a community fork of Google's original AFL with improved mutations and instrumentation). Covers afl-cc / afl-clang-fast instrumented build, afl-fuzz invocation, parallel master/slave (-M / -S), dictionary support (-x), QEMU mode (-Q) for binaries without source, output structure (queue / crashes / hangs), crash minimisation (afl-tmin), corpus minimisation (afl-cmin), crash filename triage, and CI integration. Use for fuzzing standalone binaries (file processors, command-line tools) where libFuzzer's in-process model doesn't fit; for cross-fuzzer corpus strategy see corpus-management-reference.

atheris-python-fuzzing

Author and run Atheris - Google's Python coverage-guided fuzzer built on libFuzzer. Covers pip installation, atheris.Setup + atheris.Fuzz invocation, TestOneInput(data: bytes) target signature, FuzzedDataProvider for structured input, instrument_imports() / instrument_func decorators for coverage instrumentation, and libFuzzer-passthrough flags (-atheris_runs, -max_total_time, -dict). Use for fuzzing Python libraries - also supports CPython native-extension fuzzing.

cargo-fuzz-rust

Author and run cargo-fuzz - Rust fuzzing via libFuzzer with cargo integration. Covers `cargo install cargo-fuzz`, `cargo fuzz init` + `cargo fuzz add {target}` for harness scaffolding, the `fuzz_target!` macro for entry-point declaration, the `Arbitrary` trait for structured input mutation, and `cargo fuzz run` invocation. Requires Rust nightly. Use for fuzz testing Rust libraries - cargo-fuzz wraps libFuzzer with native Rust ergonomics.

corpus-management-reference

Pure-reference catalog of fuzz-corpus management practices. Defines what a corpus is (seed corpus + evolved corpus saved by the fuzzer), corpus directory layout per libFuzzer / AFL++ / Go native / cargo-fuzz / OSS-Fuzz, the canonical crash-artefact naming (crash-{sha1} / leak-{sha1} / timeout-{sha1}), seed corpus construction strategies (sample-from-prod, sample-from-test-fixtures, from-spec-keywords), corpus minimisation, dictionary files, and the OSS-Fuzz integration corpus sync. Use as the corpus-discipline reference when building a fuzz target or maintaining a long-running fuzz campaign.

crash-triage-reference

Pure-reference catalog for manually triaging individual fuzzer crash artifacts - reading ASan, UBSan, and MSan output; classifying findings as LIKELY-EXPLOITABLE, MEDIUM, or BENIGN; deduplicating by stack-hash; and minimizing reproducers with -minimize_crash. Use when you need to understand what a specific crash means, build exploitability intuition, or manually work a small set of findings. For automated bulk triage across a full artifact directory, run automated findings triage instead.

fuzz-tool-selector

Routes a fuzz-target authoring task to the right fuzzer for the detected language and build type. Decision tree: C/C++ → libfuzzer-cpp + afl-plus-plus; Rust → cargo-fuzz-rust (or libfuzzer-cpp via FFI); Go → go-native-fuzzing; Python → atheris-python-fuzzing; JVM → jazzer-jvm-fuzzing; closed-source binary → afl-plus-plus in QEMU mode; mature open-source project → ossfuzz-integration. Use when a project needs coverage-guided fuzzing and no fuzzer has been chosen for its language or toolchain yet.

go-native-fuzzing

Author and run Go's native fuzzing (Go 1.18+) - coverage-guided fuzzing built into the standard testing package via FuzzXxx functions. Covers f.Add seed-corpus declaration, f.Fuzz callback signature with typed parameters, testdata/fuzz/{FuzzXxx}/ directory layout for seeds + regression cases, the -fuzz flag for `go test`, and CI integration via short smoke runs. Use for fuzz testing Go libraries - Go's native approach integrates seamlessly with standard `go test` rather than requiring a separate toolchain like AFL++.

jazzer-jvm-fuzzing

Author and run Jazzer - Code Intelligence's JVM coverage-guided fuzzer built on libFuzzer. Covers Maven / Gradle / standalone JAR installation, the @FuzzTest annotation (JUnit 5 integration), typed parameter mutation (String, primitives, byte[]), built-in JVM sanitisers (SSRF / path traversal / OS command injection / deserialization gadget / ReDoS), and the JAZZER_FUZZ=1 env var to switch between regression and fuzzing modes. Use for fuzz testing Java / Kotlin libraries - particularly effective against parsing, deserialization, and HTTP-handling code.

libfuzzer-cpp

Author and run LLVM libFuzzer for C/C++ - in-process coverage-guided fuzzing. Covers harness authoring (LLVMFuzzerTestOneInput entry point), build with -fsanitize=fuzzer,address,undefined, runtime flags (-max_total_time, -runs, -dict, -fork, -workers), corpus + crash-artefact handling, and CI integration. Use for libraries / parsers / decoders in C/C++ where in-process fuzzing of a function is the right scope. Compose with ASan + UBSan from sanitiser-integration-reference and corpus discipline from corpus-management-reference.

ossfuzz-integration

Author and submit a project to Google OSS-Fuzz - the open-source continuous fuzzing service that runs libFuzzer / AFL++ / Honggfuzz campaigns on Google infrastructure 24x7. Covers the project.yaml + Dockerfile + build.sh contract, the $OUT/$WORK conventions, supported languages + sanitisers, seed-corpus + dictionary submission, the OSS-Fuzz Build Status dashboard, and the disclosure SLA (issues filed in Monorail with 90-day deadline). Use to offload long-running fuzz campaigns to dedicated infrastructure rather than self-hosting.