Claude Skill

atheris

Sets up and runs Atheris, the coverage-guided Python fuzzer built on libFuzzer. Covers TestOneInput harnesses, FuzzedDataProvider, instrumenting both pure Python and native C extensions, and running under AddressSanitizer. Use when fuzzing a Python package, hunting memory corrupt

LLM Mart · 0 points · 10 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download trailofbits-skills-plugins_testing-handbook-skills_skills_atheris-123037e.zip · 9 KB
trailofbits/skills 7234 616 forks CC-BY-SA-4.0 Updated 11h ago
Part of trailofbits/skills — 100 skills

Install

skills CLI npx skills add https://github.com/trailofbits/skills/tree/main/plugins/testing-handbook-skills/skills/atheris
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install trailofbits-skills@llmmart
Git git clone https://github.com/trailofbits/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole trailofbits/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Atheris

Atheris is a coverage-guided Python fuzzer built on libFuzzer. It enables fuzzing of both pure Python code and Python C extensions with integrated AddressSanitizer support for detecting memory corruption issues.

When to Use

Fuzzer Best For Complexity
Atheris Python code and C extensions Low-Medium
Hypothesis Property-based testing Low
python-afl AFL-style fuzzing Medium

Choose Atheris when:

  • Fuzzing pure Python code with coverage guidance
  • Testing Python C extensions for memory corruption
  • Integration with libFuzzer ecosystem is desired
  • AddressSanitizer support is needed

Quick Start

import sys
import atheris

@atheris.instrument_func
def TestOneInput(data: bytes):
    if len(data) == 4:
        if data[0] == 0x46:  # "F"
            if data[1] == 0x55:  # "U"
                if data[2] == 0x5A:  # "Z"
                    if data[3] == 0x5A:  # "Z"
                        raise RuntimeError("You caught me")

def main():
    atheris.Setup(sys.argv, TestOneInput)
    atheris.Fuzz()

if __name__ == "__main__":
    main()

Run:

uv run python fuzz.py

Installation

Atheris supports 32-bit and 64-bit Linux, and macOS. We recommend fuzzing on Linux because it's simpler to manage and often faster.

Prerequisites

Linux/macOS

uv init --bare   # once, if the harness directory is not yet a uv project
uv add atheris

Docker Environment (Recommended)

For a fully operational Linux environment with all dependencies configured:

# https://hub.docker.com/_/python
ARG PYTHON_VERSION=3.11

FROM python:$PYTHON_VERSION-slim-bookworm

RUN python --version

RUN apt update && apt install -y \
    ca-certificates \
    wget \
    && rm -rf /var/lib/apt/lists/*

# LLVM builds version 15-19 for Debian 12 (Bookworm)
# https://apt.llvm.org/bookworm/dists/
ARG LLVM_VERSION=19

RUN echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-$LLVM_VERSION main" > /etc/apt/sources.list.d/llvm.list
RUN echo "deb-src http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-$LLVM_VERSION main" >> /etc/apt/sources.list.d/llvm.list
RUN wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key > /etc/apt/trusted.gpg.d/apt.llvm.org.asc

RUN apt update && apt install -y \
    build-essential \
    clang-$LLVM_VERSION \
    && rm -rf /var/lib/apt/lists/*

ENV APP_DIR "/app"
RUN mkdir $APP_DIR
WORKDIR $APP_DIR

ENV VIRTUAL_ENV "/opt/venv"
RUN python -m venv $VIRTUAL_ENV
ENV PATH "$VIRTUAL_ENV/bin:$PATH"

# https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#step-1-compiling-your-extension
ENV CC="clang-$LLVM_VERSION"
ENV CFLAGS "-fsanitize=address,fuzzer-no-link"
ENV CXX="clang++-$LLVM_VERSION"
ENV CXXFLAGS "-fsanitize=address,fuzzer-no-link"
ENV LDSHARED="clang-$LLVM_VERSION -shared"
ENV LDSHAREDXX="clang++-$LLVM_VERSION -shared"
ENV ASAN_SYMBOLIZER_PATH="/usr/bin/llvm-symbolizer-$LLVM_VERSION"

# Allow Atheris to find fuzzer sanitizer shared libs
# https://github.com/google/atheris#building-from-source
RUN LIBFUZZER_LIB=$($CC -print-file-name=libclang_rt.fuzzer_no_main-$(uname -m).a) \
    python -m pip install --no-binary atheris atheris

# https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#option-a-sanitizerlibfuzzer-preloads
ENV LD_PRELOAD "$VIRTUAL_ENV/lib/python3.11/site-packages/asan_with_fuzzer.so"

# 1. Skip memory allocation failures for now, they are common, and low impact (DoS)
# 2. https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#leak-detection
ENV ASAN_OPTIONS "allocator_may_return_null=1,detect_leaks=0"

CMD ["/bin/bash"]

Build and run:

docker build -t atheris .
docker run -it atheris

Verification

python -c "import atheris; print(atheris.__version__)"

Writing a Harness

Harness Structure for Pure Python

import sys
import atheris

@atheris.instrument_func
def TestOneInput(data: bytes):
    """
    Fuzzing entry point. Called with random byte sequences.

    Args:
        data: Random bytes generated by the fuzzer
    """
    # Add input validation if needed
    if len(data) < 1:
        return

    # Call your target function
    try:
        your_target_function(data)
    except ValueError:
        # Expected exceptions should be caught
        pass
    # Let unexpected exceptions crash (that's what we're looking for!)

def main():
    atheris.Setup(sys.argv, TestOneInput)
    atheris.Fuzz()

if __name__ == "__main__":
    main()

Structured Input with FuzzedDataProvider

A target taking several typed arguments wastes most of the fuzzer's inputs if the harness slices data by hand, because every mutation shifts the byte offsets of everything after it. atheris.FuzzedDataProvider splits one bytes input into typed values instead:

fdp = atheris.FuzzedDataProvider(data)
name = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 64))
strict = fdp.ConsumeBool()

See structured-input.md for the full method reference, the fixed-draw- order rule, and what each method returns once the buffer runs dry.

Harness Rules

Do Don't
Use @atheris.instrument_func for coverage Forget to instrument target code
Catch expected exceptions Catch all exceptions indiscriminately
Use atheris.instrument_imports() for libraries Import modules after atheris.Setup()
Keep harness deterministic Use randomness or time-based behavior

See Also: For detailed harness writing techniques, patterns for handling complex inputs, and advanced strategies, see the fuzz-harness-writing technique skill.

Fuzzing Pure Python Code

For fuzzing broader parts of an application or library, use instrumentation functions:

import atheris
with atheris.instrument_imports():
    import your_module
    from another_module import target_function

def TestOneInput(data: bytes):
    target_function(data)

atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()

Instrumentation Options:

  • atheris.instrument_func - Decorator for single function instrumentation
  • atheris.instrument_imports() - Context manager for instrumenting all imported modules
  • atheris.instrument_all() - Instrument all Python code system-wide

Fuzzing Python C Extensions

Python C extensions require compilation with specific flags for instrumentation and sanitizer support.

Environment Configuration

If using the provided Dockerfile, these are already configured. For local setup:

export CC="clang"
export CFLAGS="-fsanitize=address,fuzzer-no-link"
export CXX="clang++"
export CXXFLAGS="-fsanitize=address,fuzzer-no-link"
export LDSHARED="clang -shared"

Example: Fuzzing cbor2

Install the extension from source:

CBOR2_BUILD_C_EXTENSION=1 uv add --no-binary-package cbor2 'cbor2==5.6.4'

The --no-binary-package flag ensures the C extension is compiled locally with instrumentation rather than pulled as a prebuilt wheel. Persist that choice with no-binary-package = ["cbor2"] under [tool.uv] in pyproject.toml, or a later uv sync can silently swap in an uninstrumented wheel.

Create cbor2-fuzz.py:

import sys
import atheris

# _cbor2 ensures the C library is imported
from _cbor2 import loads

def TestOneInput(data: bytes):
    try:
        loads(data)
    except Exception:
        # We're searching for memory corruption, not Python exceptions
        pass

def main():
    atheris.Setup(sys.argv, TestOneInput)
    atheris.Fuzz()

if __name__ == "__main__":
    main()

Run:

uv run python cbor2-fuzz.py

Important: When running locally (not in Docker), you must set LD_PRELOAD manually.

Corpus Management

Creating Initial Corpus

mkdir corpus
# Add seed inputs
echo "test data" > corpus/seed1
echo '{"key": "value"}' > corpus/seed2

Run with corpus:

uv run python fuzz.py corpus/

Corpus Minimization

Atheris inherits corpus minimization from libFuzzer:

uv run python fuzz.py -merge=1 new_corpus/ old_corpus/

See Also: For corpus creation strategies, dictionaries, and seed selection, see the fuzzing-corpus technique skill.

Running Campaigns

Basic Run

uv run python fuzz.py

With Corpus Directory

uv run python fuzz.py corpus/

Common Options

# Run for 10 minutes
uv run python fuzz.py -max_total_time=600

# Limit input size
uv run python fuzz.py -max_len=1024

# Run with multiple workers
uv run python fuzz.py -workers=4 -jobs=4

Interpreting Output

Output Meaning
NEW cov: X Found new coverage, corpus expanded
pulse cov: X Periodic status update
exec/s: X Executions per second (throughput)
corp: X/Yb Corpus size: X inputs, Y bytes total
ERROR: libFuzzer Crash detected

Sanitizer Integration

AddressSanitizer (ASan)

AddressSanitizer is automatically integrated when using the provided Docker environment or when compiling with appropriate flags.

For local setup:

export CFLAGS="-fsanitize=address,fuzzer-no-link"
export CXXFLAGS="-fsanitize=address,fuzzer-no-link"

Configure ASan behavior:

export ASAN_OPTIONS="allocator_may_return_null=1,detect_leaks=0"

LD_PRELOAD Configuration

For native extension fuzzing:

export LD_PRELOAD="$(python -c 'import atheris; import os; print(os.path.join(os.path.dirname(atheris.__file__), "asan_with_fuzzer.so"))')"

See Also: For detailed sanitizer configuration, common issues, and advanced flags, see the address-sanitizer and undefined-behavior-sanitizer technique skills.

Common Sanitizer Issues

Issue Solution
LD_PRELOAD not set Export LD_PRELOAD to point to asan_with_fuzzer.so
Memory allocation failures Set ASAN_OPTIONS=allocator_may_return_null=1
Leak detection noise Set ASAN_OPTIONS=detect_leaks=0
Missing symbolizer Set ASAN_SYMBOLIZER_PATH to llvm-symbolizer

Advanced Usage

Tips and Tricks

Tip Why It Helps
Use atheris.instrument_imports() early Ensures all imports are instrumented for coverage
Start with small max_len Faster initial fuzzing, gradually increase
Use dictionaries for structured formats Helps fuzzer understand format tokens
Run multiple parallel instances Better coverage exploration

Custom Instrumentation

Fine-tune what gets instrumented:

import atheris

# Instrument only specific modules
with atheris.instrument_imports():
    import target_module
# Don't instrument test harness code

def TestOneInput(data: bytes):
    target_module.parse(data)

Performance Tuning

Setting Impact
-max_len=N Smaller values = faster execution
-workers=N -jobs=N Parallel fuzzing for faster coverage
ASAN_OPTIONS=fast_unwind_on_malloc=0 Better stack traces, slower execution

UndefinedBehaviorSanitizer (UBSan)

Add UBSan to catch additional bugs:

export CFLAGS="-fsanitize=address,undefined,fuzzer-no-link"
export CXXFLAGS="-fsanitize=address,undefined,fuzzer-no-link"

Note: Modify flags in Dockerfile if using containerized setup.

Real-World Examples

Two complete harnesses — a pure-Python parser and an HTTP response parser — are in examples.md.

Troubleshooting

Problem Cause Solution
No coverage increase Poor seed corpus or target not instrumented Add better seeds, verify instrument_imports()
Slow execution ASan overhead or large inputs Reduce max_len, use ASAN_OPTIONS=fast_unwind_on_malloc=1
Import errors Modules imported before instrumentation Move imports inside instrument_imports() context
Segfault without ASan output Missing LD_PRELOAD Set LD_PRELOAD to asan_with_fuzzer.so path
Build failures Wrong compiler or missing flags Verify CC, CFLAGS, and clang version

Related Skills

Technique Skills

Skill Use Case
fuzz-harness-writing Detailed guidance on writing effective harnesses
address-sanitizer Memory error detection during fuzzing
undefined-behavior-sanitizer Catching undefined behavior in C extensions
coverage-analysis Measuring and improving code coverage
fuzzing-corpus Building and managing seed corpora

Related Fuzzers

Skill When to Consider
hypothesis Property-based testing with type-aware generation
python-afl AFL-style fuzzing for Python when Atheris isn't available

Resources

Key External Resources

Atheris GitHub Repository Official repository with installation instructions, examples, and documentation for fuzzing both pure Python and native extensions.

Native Extension Fuzzing Guide Comprehensive guide covering compilation flags, LD_PRELOAD setup, sanitizer configuration, and troubleshooting for Python C extensions.

Continuously Fuzzing Python C Extensions Trail of Bits blog post covering CI/CD integration, ClusterFuzzLite setup, and real-world examples of fuzzing Python C extensions in continuous integration pipelines.

ClusterFuzzLite Python Integration Guide for integrating Atheris fuzzing into CI/CD pipelines using ClusterFuzzLite for automated continuous fuzzing.

Video Resources

Videos and tutorials are available in the main Atheris documentation and libFuzzer resources.

Files (skills)
  • agents
    • openai.yaml 237 B
      interface:
        display_name: "Atheris Fuzzing"
        short_description: "Fuzz Python code and native extensions with Atheris"
        icon_small: "assets/trail-of-bits-mark.svg"
        icon_large: "assets/trail-of-bits-mark.svg"
        brand_color: "#D83A34"
      
  • assets
    • trail-of-bits-mark.svg 3 KB · in bundle
  • examples.md 1.3 KB
    # Atheris Examples
    
    Two complete harnesses, each runnable as written.
    
    ## Example: Pure Python Parser
    
    ```python
    import sys
    import atheris
    import json
    
    @atheris.instrument_func
    def TestOneInput(data: bytes):
        try:
            # Fuzz Python's JSON parser
            json.loads(data.decode('utf-8', errors='ignore'))
        except (ValueError, UnicodeDecodeError):
            pass
    
    def main():
        atheris.Setup(sys.argv, TestOneInput)
        atheris.Fuzz()
    
    if __name__ == "__main__":
        main()
    ```
    
    ## Example: HTTP Request Parsing
    
    ```python
    import sys
    import atheris
    
    with atheris.instrument_imports():
        from urllib3 import HTTPResponse
        from io import BytesIO
    
    def TestOneInput(data: bytes):
        try:
            # Fuzz HTTP response parsing
            fake_response = HTTPResponse(
                body=BytesIO(data),
                headers={},
                preload_content=False
            )
            fake_response.read()
        except Exception:
            pass
    
    def main():
        atheris.Setup(sys.argv, TestOneInput)
        atheris.Fuzz()
    
    if __name__ == "__main__":
        main()
    ```
    
    Both catch `Exception` broadly to get a campaign started. Narrow that to the exceptions the
    target is documented to raise before you trust the results — a bare `except Exception` also
    swallows the bugs you are fuzzing for.
    
  • SKILL.md 14.4 KB
    ---
    name: atheris
    type: fuzzer
    description: "Sets up and runs Atheris, the coverage-guided Python fuzzer built on libFuzzer. Covers TestOneInput harnesses, FuzzedDataProvider, instrumenting both pure Python and native C extensions, and running under AddressSanitizer. Use when fuzzing a Python package, hunting memory corruption in a Python C extension, or choosing between Atheris and Hypothesis for a Python target."
    ---
    
    # Atheris
    
    Atheris is a coverage-guided Python fuzzer built on libFuzzer. It enables fuzzing of both pure Python code and Python C extensions with integrated AddressSanitizer support for detecting memory corruption issues.
    
    ## When to Use
    
    | Fuzzer | Best For | Complexity |
    |--------|----------|------------|
    | Atheris | Python code and C extensions | Low-Medium |
    | Hypothesis | Property-based testing | Low |
    | python-afl | AFL-style fuzzing | Medium |
    
    **Choose Atheris when:**
    - Fuzzing pure Python code with coverage guidance
    - Testing Python C extensions for memory corruption
    - Integration with libFuzzer ecosystem is desired
    - AddressSanitizer support is needed
    
    ## Quick Start
    
    ```python
    import sys
    import atheris
    
    @atheris.instrument_func
    def TestOneInput(data: bytes):
        if len(data) == 4:
            if data[0] == 0x46:  # "F"
                if data[1] == 0x55:  # "U"
                    if data[2] == 0x5A:  # "Z"
                        if data[3] == 0x5A:  # "Z"
                            raise RuntimeError("You caught me")
    
    def main():
        atheris.Setup(sys.argv, TestOneInput)
        atheris.Fuzz()
    
    if __name__ == "__main__":
        main()
    ```
    
    Run:
    ```bash
    uv run python fuzz.py
    ```
    
    ## Installation
    
    Atheris supports 32-bit and 64-bit Linux, and macOS. We recommend fuzzing on Linux because it's simpler to manage and often faster.
    
    ### Prerequisites
    
    - Python 3.7 or later
    - Recent version of clang (preferably [latest release](https://github.com/llvm/llvm-project/releases))
    - For Docker users: [Docker Desktop](https://www.docker.com/products/docker-desktop/)
    
    ### Linux/macOS
    
    ```bash
    uv init --bare   # once, if the harness directory is not yet a uv project
    uv add atheris
    ```
    
    ### Docker Environment (Recommended)
    
    For a fully operational Linux environment with all dependencies configured:
    
    ```dockerfile
    # https://hub.docker.com/_/python
    ARG PYTHON_VERSION=3.11
    
    FROM python:$PYTHON_VERSION-slim-bookworm
    
    RUN python --version
    
    RUN apt update && apt install -y \
        ca-certificates \
        wget \
        && rm -rf /var/lib/apt/lists/*
    
    # LLVM builds version 15-19 for Debian 12 (Bookworm)
    # https://apt.llvm.org/bookworm/dists/
    ARG LLVM_VERSION=19
    
    RUN echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-$LLVM_VERSION main" > /etc/apt/sources.list.d/llvm.list
    RUN echo "deb-src http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-$LLVM_VERSION main" >> /etc/apt/sources.list.d/llvm.list
    RUN wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key > /etc/apt/trusted.gpg.d/apt.llvm.org.asc
    
    RUN apt update && apt install -y \
        build-essential \
        clang-$LLVM_VERSION \
        && rm -rf /var/lib/apt/lists/*
    
    ENV APP_DIR "/app"
    RUN mkdir $APP_DIR
    WORKDIR $APP_DIR
    
    ENV VIRTUAL_ENV "/opt/venv"
    RUN python -m venv $VIRTUAL_ENV
    ENV PATH "$VIRTUAL_ENV/bin:$PATH"
    
    # https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#step-1-compiling-your-extension
    ENV CC="clang-$LLVM_VERSION"
    ENV CFLAGS "-fsanitize=address,fuzzer-no-link"
    ENV CXX="clang++-$LLVM_VERSION"
    ENV CXXFLAGS "-fsanitize=address,fuzzer-no-link"
    ENV LDSHARED="clang-$LLVM_VERSION -shared"
    ENV LDSHAREDXX="clang++-$LLVM_VERSION -shared"
    ENV ASAN_SYMBOLIZER_PATH="/usr/bin/llvm-symbolizer-$LLVM_VERSION"
    
    # Allow Atheris to find fuzzer sanitizer shared libs
    # https://github.com/google/atheris#building-from-source
    RUN LIBFUZZER_LIB=$($CC -print-file-name=libclang_rt.fuzzer_no_main-$(uname -m).a) \
        python -m pip install --no-binary atheris atheris
    
    # https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#option-a-sanitizerlibfuzzer-preloads
    ENV LD_PRELOAD "$VIRTUAL_ENV/lib/python3.11/site-packages/asan_with_fuzzer.so"
    
    # 1. Skip memory allocation failures for now, they are common, and low impact (DoS)
    # 2. https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#leak-detection
    ENV ASAN_OPTIONS "allocator_may_return_null=1,detect_leaks=0"
    
    CMD ["/bin/bash"]
    ```
    
    Build and run:
    ```bash
    docker build -t atheris .
    docker run -it atheris
    ```
    
    ### Verification
    
    ```bash
    python -c "import atheris; print(atheris.__version__)"
    ```
    
    ## Writing a Harness
    
    ### Harness Structure for Pure Python
    
    ```python
    import sys
    import atheris
    
    @atheris.instrument_func
    def TestOneInput(data: bytes):
        """
        Fuzzing entry point. Called with random byte sequences.
    
        Args:
            data: Random bytes generated by the fuzzer
        """
        # Add input validation if needed
        if len(data) < 1:
            return
    
        # Call your target function
        try:
            your_target_function(data)
        except ValueError:
            # Expected exceptions should be caught
            pass
        # Let unexpected exceptions crash (that's what we're looking for!)
    
    def main():
        atheris.Setup(sys.argv, TestOneInput)
        atheris.Fuzz()
    
    if __name__ == "__main__":
        main()
    ```
    
    ### Structured Input with FuzzedDataProvider
    
    A target taking several typed arguments wastes most of the fuzzer's inputs if the harness
    slices `data` by hand, because every mutation shifts the byte offsets of everything after it.
    `atheris.FuzzedDataProvider` splits one `bytes` input into typed values instead:
    
    ```python
    fdp = atheris.FuzzedDataProvider(data)
    name = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 64))
    strict = fdp.ConsumeBool()
    ```
    
    See [structured-input.md](structured-input.md) for the full method reference, the fixed-draw-
    order rule, and what each method returns once the buffer runs dry.
    
    ### Harness Rules
    
    | Do | Don't |
    |----|-------|
    | Use `@atheris.instrument_func` for coverage | Forget to instrument target code |
    | Catch expected exceptions | Catch all exceptions indiscriminately |
    | Use `atheris.instrument_imports()` for libraries | Import modules after `atheris.Setup()` |
    | Keep harness deterministic | Use randomness or time-based behavior |
    
    > **See Also:** For detailed harness writing techniques, patterns for handling complex inputs,
    > and advanced strategies, see the **fuzz-harness-writing** technique skill.
    
    ## Fuzzing Pure Python Code
    
    For fuzzing broader parts of an application or library, use instrumentation functions:
    
    ```python
    import atheris
    with atheris.instrument_imports():
        import your_module
        from another_module import target_function
    
    def TestOneInput(data: bytes):
        target_function(data)
    
    atheris.Setup(sys.argv, TestOneInput)
    atheris.Fuzz()
    ```
    
    **Instrumentation Options:**
    - `atheris.instrument_func` - Decorator for single function instrumentation
    - `atheris.instrument_imports()` - Context manager for instrumenting all imported modules
    - `atheris.instrument_all()` - Instrument all Python code system-wide
    
    ## Fuzzing Python C Extensions
    
    Python C extensions require compilation with specific flags for instrumentation and sanitizer support.
    
    ### Environment Configuration
    
    If using the provided Dockerfile, these are already configured. For local setup:
    
    ```bash
    export CC="clang"
    export CFLAGS="-fsanitize=address,fuzzer-no-link"
    export CXX="clang++"
    export CXXFLAGS="-fsanitize=address,fuzzer-no-link"
    export LDSHARED="clang -shared"
    ```
    
    ### Example: Fuzzing cbor2
    
    Install the extension from source:
    ```bash
    CBOR2_BUILD_C_EXTENSION=1 uv add --no-binary-package cbor2 'cbor2==5.6.4'
    ```
    
    The `--no-binary-package` flag ensures the C extension is compiled locally with
    instrumentation rather than pulled as a prebuilt wheel. Persist that choice with
    `no-binary-package = ["cbor2"]` under `[tool.uv]` in `pyproject.toml`, or a later
    `uv sync` can silently swap in an uninstrumented wheel.
    
    Create `cbor2-fuzz.py`:
    ```python
    import sys
    import atheris
    
    # _cbor2 ensures the C library is imported
    from _cbor2 import loads
    
    def TestOneInput(data: bytes):
        try:
            loads(data)
        except Exception:
            # We're searching for memory corruption, not Python exceptions
            pass
    
    def main():
        atheris.Setup(sys.argv, TestOneInput)
        atheris.Fuzz()
    
    if __name__ == "__main__":
        main()
    ```
    
    Run:
    ```bash
    uv run python cbor2-fuzz.py
    ```
    
    > **Important:** When running locally (not in Docker), you must [set `LD_PRELOAD` manually](https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#option-a-sanitizerlibfuzzer-preloads).
    
    ## Corpus Management
    
    ### Creating Initial Corpus
    
    ```bash
    mkdir corpus
    # Add seed inputs
    echo "test data" > corpus/seed1
    echo '{"key": "value"}' > corpus/seed2
    ```
    
    Run with corpus:
    ```bash
    uv run python fuzz.py corpus/
    ```
    
    ### Corpus Minimization
    
    Atheris inherits corpus minimization from libFuzzer:
    ```bash
    uv run python fuzz.py -merge=1 new_corpus/ old_corpus/
    ```
    
    > **See Also:** For corpus creation strategies, dictionaries, and seed selection,
    > see the **fuzzing-corpus** technique skill.
    
    ## Running Campaigns
    
    ### Basic Run
    
    ```bash
    uv run python fuzz.py
    ```
    
    ### With Corpus Directory
    
    ```bash
    uv run python fuzz.py corpus/
    ```
    
    ### Common Options
    
    ```bash
    # Run for 10 minutes
    uv run python fuzz.py -max_total_time=600
    
    # Limit input size
    uv run python fuzz.py -max_len=1024
    
    # Run with multiple workers
    uv run python fuzz.py -workers=4 -jobs=4
    ```
    
    ### Interpreting Output
    
    | Output | Meaning |
    |--------|---------|
    | `NEW    cov: X` | Found new coverage, corpus expanded |
    | `pulse  cov: X` | Periodic status update |
    | `exec/s: X` | Executions per second (throughput) |
    | `corp: X/Yb` | Corpus size: X inputs, Y bytes total |
    | `ERROR: libFuzzer` | Crash detected |
    
    ## Sanitizer Integration
    
    ### AddressSanitizer (ASan)
    
    AddressSanitizer is automatically integrated when using the provided Docker environment or when compiling with appropriate flags.
    
    For local setup:
    ```bash
    export CFLAGS="-fsanitize=address,fuzzer-no-link"
    export CXXFLAGS="-fsanitize=address,fuzzer-no-link"
    ```
    
    Configure ASan behavior:
    ```bash
    export ASAN_OPTIONS="allocator_may_return_null=1,detect_leaks=0"
    ```
    
    ### LD_PRELOAD Configuration
    
    For native extension fuzzing:
    ```bash
    export LD_PRELOAD="$(python -c 'import atheris; import os; print(os.path.join(os.path.dirname(atheris.__file__), "asan_with_fuzzer.so"))')"
    ```
    
    > **See Also:** For detailed sanitizer configuration, common issues, and advanced flags,
    > see the **address-sanitizer** and **undefined-behavior-sanitizer** technique skills.
    
    ### Common Sanitizer Issues
    
    | Issue | Solution |
    |-------|----------|
    | `LD_PRELOAD` not set | Export `LD_PRELOAD` to point to `asan_with_fuzzer.so` |
    | Memory allocation failures | Set `ASAN_OPTIONS=allocator_may_return_null=1` |
    | Leak detection noise | Set `ASAN_OPTIONS=detect_leaks=0` |
    | Missing symbolizer | Set `ASAN_SYMBOLIZER_PATH` to `llvm-symbolizer` |
    
    ## Advanced Usage
    
    ### Tips and Tricks
    
    | Tip | Why It Helps |
    |-----|--------------|
    | Use `atheris.instrument_imports()` early | Ensures all imports are instrumented for coverage |
    | Start with small `max_len` | Faster initial fuzzing, gradually increase |
    | Use dictionaries for structured formats | Helps fuzzer understand format tokens |
    | Run multiple parallel instances | Better coverage exploration |
    
    ### Custom Instrumentation
    
    Fine-tune what gets instrumented:
    ```python
    import atheris
    
    # Instrument only specific modules
    with atheris.instrument_imports():
        import target_module
    # Don't instrument test harness code
    
    def TestOneInput(data: bytes):
        target_module.parse(data)
    ```
    
    ### Performance Tuning
    
    | Setting | Impact |
    |---------|--------|
    | `-max_len=N` | Smaller values = faster execution |
    | `-workers=N -jobs=N` | Parallel fuzzing for faster coverage |
    | `ASAN_OPTIONS=fast_unwind_on_malloc=0` | Better stack traces, slower execution |
    
    ### UndefinedBehaviorSanitizer (UBSan)
    
    Add UBSan to catch additional bugs:
    ```bash
    export CFLAGS="-fsanitize=address,undefined,fuzzer-no-link"
    export CXXFLAGS="-fsanitize=address,undefined,fuzzer-no-link"
    ```
    
    Note: Modify flags in Dockerfile if using containerized setup.
    
    ## Real-World Examples
    
    Two complete harnesses — a pure-Python parser and an HTTP response parser — are in
    [examples.md](examples.md).
    
    ## Troubleshooting
    
    | Problem | Cause | Solution |
    |---------|-------|----------|
    | No coverage increase | Poor seed corpus or target not instrumented | Add better seeds, verify `instrument_imports()` |
    | Slow execution | ASan overhead or large inputs | Reduce `max_len`, use `ASAN_OPTIONS=fast_unwind_on_malloc=1` |
    | Import errors | Modules imported before instrumentation | Move imports inside `instrument_imports()` context |
    | Segfault without ASan output | Missing `LD_PRELOAD` | Set `LD_PRELOAD` to `asan_with_fuzzer.so` path |
    | Build failures | Wrong compiler or missing flags | Verify `CC`, `CFLAGS`, and clang version |
    
    ## Related Skills
    
    ### Technique Skills
    
    | Skill | Use Case |
    |-------|----------|
    | **fuzz-harness-writing** | Detailed guidance on writing effective harnesses |
    | **address-sanitizer** | Memory error detection during fuzzing |
    | **undefined-behavior-sanitizer** | Catching undefined behavior in C extensions |
    | **coverage-analysis** | Measuring and improving code coverage |
    | **fuzzing-corpus** | Building and managing seed corpora |
    
    ### Related Fuzzers
    
    | Skill | When to Consider |
    |-------|------------------|
    | **hypothesis** | Property-based testing with type-aware generation |
    | **python-afl** | AFL-style fuzzing for Python when Atheris isn't available |
    
    ## Resources
    
    ### Key External Resources
    
    **[Atheris GitHub Repository](https://github.com/google/atheris)**
    Official repository with installation instructions, examples, and documentation for fuzzing both pure Python and native extensions.
    
    **[Native Extension Fuzzing Guide](https://github.com/google/atheris/blob/master/native_extension_fuzzing.md)**
    Comprehensive guide covering compilation flags, LD_PRELOAD setup, sanitizer configuration, and troubleshooting for Python C extensions.
    
    **[Continuously Fuzzing Python C Extensions](https://blog.trailofbits.com/2024/02/23/continuously-fuzzing-python-c-extensions/)**
    Trail of Bits blog post covering CI/CD integration, ClusterFuzzLite setup, and real-world examples of fuzzing Python C extensions in continuous integration pipelines.
    
    **[ClusterFuzzLite Python Integration](https://google.github.io/clusterfuzzlite/build-integration/python-lang/)**
    Guide for integrating Atheris fuzzing into CI/CD pipelines using ClusterFuzzLite for automated continuous fuzzing.
    
    ### Video Resources
    
    Videos and tutorials are available in the main Atheris documentation and libFuzzer resources.
    
  • structured-input.md 3.3 KB
    # Structured Input with FuzzedDataProvider
    
    A target that takes several typed arguments — a string, a length, a flag — wastes most of
    the fuzzer's inputs if the harness slices `data` by hand, because every mutation shifts the
    byte offsets of everything after it. `atheris.FuzzedDataProvider` splits one `bytes` input
    into typed values while keeping each draw stable under mutation.
    
    ## Basic Usage
    
    ```python
    @atheris.instrument_func
    def TestOneInput(data: bytes):
        fdp = atheris.FuzzedDataProvider(data)
        name = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 64))
        count = fdp.ConsumeIntInRange(1, 1000)
        strict = fdp.ConsumeBool()
        your_target_function(name, count, strict=strict)
    ```
    
    Draw in a fixed order. Each call consumes from where the last one left off, so inserting or
    reordering a call reinterprets every byte after it and devalues the corpus already built.
    
    Ask for a size before the content it bounds, as above: an unbounded string lets the fuzzer
    spend the whole buffer on one field and starve every draw after it.
    
    ## Method Reference
    
    | Need | Call |
    |------|------|
    | Raw bytes | `ConsumeBytes(count)` |
    | Text | `ConsumeUnicodeNoSurrogates(count)` |
    | Text, including unpaired surrogates | `ConsumeUnicode(count)` |
    | Bounded integer | `ConsumeIntInRange(min, max)` |
    | Sized integer | `ConsumeInt(size)` (signed), `ConsumeUInt(size)` |
    | Float | `ConsumeFloat()`, `ConsumeRegularFloat()` (no `NaN`/`Inf`), `ConsumeProbability()` |
    | Flag | `ConsumeBool()` |
    | Choice from a fixed set | `PickValueInList(values)` |
    | Everything not yet consumed | `ConsumeBytes(fdp.remaining_bytes())` |
    
    `remaining_bytes()` is an accessor, not a draw — it returns the count of unconsumed bytes and
    consumes nothing. It is the one method here that is not named `Consume*`, and the only one
    whose return value is a length rather than a value. Pass it to `ConsumeBytes` to drain the
    buffer; using it directly hands your target an `int` where it expects `bytes`.
    
    Prefer `ConsumeUnicodeNoSurrogates` unless you are specifically testing surrogate handling.
    `ConsumeUnicode` may emit unpaired surrogates (U+D800–U+DFFF), which are legal in a Python
    `str` but raise `UnicodeEncodeError` the moment the target encodes them — so a target that
    encodes anywhere reports a crash on its own input handling rather than on your target's logic.
    
    List variants take the element count first:
    
    | Call | Produces |
    |------|----------|
    | `ConsumeIntList(count, bytes)` | `count` integers of `bytes` size each |
    | `ConsumeIntListInRange(count, min, max)` | `count` integers in `[min, max]` |
    | `ConsumeFloatList(count)` | `count` arbitrary floats, `NaN` and `Inf` included |
    | `ConsumeRegularFloatList(count)` | `count` floats, never `NaN` or `Inf` |
    | `ConsumeProbabilityList(count)` | `count` floats in `[0, 1]` |
    | `ConsumeFloatListInRange(count, min, max)` | `count` floats in `[min, max]` |
    
    ## Running Out of Input
    
    Every method degrades rather than raising when the buffer empties: consumers return empty
    values, and `ConsumeIntInRange` returns `min`. A harness that draws more than the fuzzer
    supplies will not error — it will quietly test the same degenerate case over and over, which
    looks like a healthy campaign that has stopped finding anything. Check `remaining_bytes()`
    and return early if your harness needs a minimum amount of input.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related