CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/coverage-guided-fuzzing

Coverage-guided fuzzing across every mainstream engine - libFuzzer (C/C++ in-process), AFL++ (out-of-process, QEMU mode for closed-source binaries), cargo-fuzz (Rust), Go native fuzzing (go test -fuzz), Atheris (Python), and Jazzer (JVM, @FuzzTest). Body covers choosing the right fuzzer for the language and build type (the routing tree) plus the engine-generic workflow: writing a small deterministic fuzz target, seed-corpus + dictionary construction, sanitizer selection (ASan + UBSan default, compatibility matrix), corpus minimisation, crash-artifact handling, and CI smoke-fuzz wiring with a cached corpus. Per-engine depth (flags, harness syntax, CI jobs) lives in references, as do the corpus-management and sanitizer-integration catalogs. Use when a project needs fuzz coverage and no fuzzer is chosen yet, or when authoring / running / maintaining a fuzz campaign with any of these engines. For triaging the resulting crashes see crash-triage-reference.

70

Quality

88%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

jazzer.mdreferences/

Jazzer (JVM)

Per-engine reference for coverage-guided-fuzzing; the shared workflow and fuzzer-choice routing live in ../SKILL.md.

Overview

Distinct from C/C++ fuzzers: Jazzer (per github.com/CodeIntelligenceTesting/jazzer) ships JVM-level sanitisers that detect security-sensitive misuse of standard APIs (deserialization gadgets, SSRF, ReDoS) - not memory-safety bugs (the JVM handles those).

For corpus discipline see corpus-management.md.

When to use

  • Fuzz testing Java / Kotlin / Scala / Groovy libraries.
  • Targets handling user input - parsers, deserialisers, HTTP handlers, URL constructors (Jazzer's JVM sanitisers catch injection bugs).
  • JUnit 5-anchored projects - Jazzer integrates as a JUnit test type.

Authoring

Install (Maven)

Per Jazzer README:

<dependency>
    <groupId>com.code-intelligence</groupId>
    <artifactId>jazzer-junit</artifactId>
    <version>${jazzer.version}</version>
    <scope>test</scope>
</dependency>

Install (Gradle)

dependencies {
    testImplementation "com.code-intelligence:jazzer-junit:$jazzerVersion"
}

Pin the version in one place - jazzer.version (Maven property) or jazzerVersion (Gradle ext) - to the latest release from Maven Central (search.maven.org/artifact/com.code-intelligence/jazzer-junit); 0.22.1 was current at time of writing.

Install (standalone)

Download binary release from GitHub; invoke jazzer --cp=<classpath>.

Fuzz target with JUnit 5

import com.code_intelligence.jazzer.junit.FuzzTest;
import org.jetbrains.annotations.NotNull;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class ParserFuzzTest {

    @FuzzTest
    void fuzzDecode(@NotNull String input) {
        assertEquals(input, SomeScheme.decode(SomeScheme.encode(input)));
    }
}

Per Jazzer docs, @FuzzTest is the annotation that registers a fuzz target. The method parameters become fuzzer-mutated typed inputs.

Supported parameter types

Per Jazzer README, @FuzzTest parameters support:

  • Primitive types (int, long, boolean, byte, char, short, float, double)
  • String
  • Arrays of primitives + arrays of String
  • Many standard library classes via auto-marshalling

Annotations refine mutation:

AnnotationEffect
@NotNullParameter never null
@WithUtf8Length(min=N, max=M)String byte-length bound
@InRange(min=N, max=M)Integer range
@FuzzTest
void fuzzWithBounds(@NotNull @WithUtf8Length(max = 256) String host,
                    @InRange(min = 1, max = 65535) int port) {
    handleRequest(host, port);
}

FuzzedDataProvider (advanced)

For complex input shapes:

import com.code_intelligence.jazzer.api.FuzzedDataProvider;

@FuzzTest
void fuzzComplex(FuzzedDataProvider data) {
    int n = data.consumeInt(0, 100);
    String s = data.consumeString(64);
    byte[] body = data.consumeRemainingAsBytes();
    process(n, s, body);
}

Running

Modes - regression vs fuzzing

Per Jazzer docs:

  • Regression mode (default): runs the test against any saved inputs in src/test/resources/<TestClass>/<methodName>/ - fast, deterministic.
  • Fuzzing mode: set JAZZER_FUZZ=1 env var; explores new inputs.
# Regression
mvn test

# Fuzzing
JAZZER_FUZZ=1 mvn test -Dtest=ParserFuzzTest#fuzzDecode

# Bounded time
JAZZER_FUZZ=300 mvn test -Dtest=ParserFuzzTest
# (specific seconds)

Standalone invocation

./jazzer \
  --cp=target/test-classes:target/classes \
  --target_class=com.example.ParserFuzzTest \
  --target_method=fuzzDecode \
  -max_total_time=300

Common flags

Jazzer accepts libFuzzer-style flags:

FlagEffect
-max_total_time=NStop after N seconds
-runs=NNumber of iterations
-dict=pathDictionary file
--keep_going=NKeep fuzzing after first crash (find N total)
--instrumentation_includes=PKG.*Limit coverage instrumentation to a package

JVM sanitisers

Jazzer's built-in detectors fire automatically on security-relevant misuse (deserialization gadgets, SSRF, path traversal, OS command injection, ReDoS, LDAP / JNDI / SQL injection) - no extra config; disable selectively via --disabled_hooks=.... Full catalogue with what each catches: see "JVM sanitiser catalogue" below.

Parsing results

When Jazzer finds a crash, output:

== Java Exception: java.lang.AssertionError: expected: <foo> but was: <bar>
    at com.example.ParserFuzzTest.fuzzDecode(ParserFuzzTest.java:12)
    ...
== libFuzzer crashing input ==
artifact_prefix='./'; Test unit written to ./crash-<sha1>
Base64: <encoded-input>
Reproducer input written to: src/test/resources/com/example/ParserFuzzTest/fuzzDecode/<sha1>

The reproducer is saved to src/test/resources/... as part of the test fixtures - commit it for regression coverage.

CI integration

Run regression inputs with mvn test, then a bounded smoke-fuzz (JAZZER_FUZZ=180) over every @FuzzTest class and upload crashes: see "Full CI job" below.

Anti-patterns

Anti-patternWhy it failsFix
Untyped byte[] parameter for structured inputForegoes Jazzer's typed-mutation advantageUse typed parameters or FuzzedDataProvider
Catching Throwable in targetHides real bugsLet exceptions propagate; use assertXxx for invariants
Skipping @NotNull annotationSpurious NPE crashesAlways annotate @NotNull unless null is legitimate
Not committing reproducer filesLose regression coverageCommit src/test/resources/<test-class>/<method>/
Disabling all JVM sanitisersLoses Jazzer's biggest advantage over plain libFuzzerKeep sanitisers enabled; disable selectively if false positives
Single-target campaignOther targets not exercisedRun all @FuzzTest methods in CI

Limitations

  • JVM startup cost. Each fuzz iteration shares the JVM, so startup is amortised - but JIT warmup still affects early iterations.
  • Allocation-heavy targets slow. GC dominates iteration time for allocation-heavy code.
  • Native (JNI) code not coverage-instrumented. For native bug-hunting in JNI libraries use libFuzzer + JNI wrappers.
  • @FuzzTest on Kotlin works but parameter mutation respects Kotlin nullability - null-tolerant Kotlin parameters fuzz with null values too.
  • Distinguishes "test failure" from "fuzz finding" loosely - any AssertionError is a finding; tune assertions deliberately.

References

JVM sanitiser catalogue

Per Jazzer README, built-in detectors fire on security-relevant misuse:

SanitiserWhat it catches
DeserializationUntrusted ObjectInputStream / XStream / Kryo input → gadget execution
SSRFURL constructed from untrusted input pointing at internal infrastructure
Path traversal.. / encoded variants in file path arguments
OS command injectionRuntime.exec / ProcessBuilder with concatenated input
ReDoSCatastrophic-backtracking regex constructed from untrusted input
LDAP injectionLDAP query string concatenation
Naming contextJNDI lookup with untrusted name
SQL injection (via Hibernate / direct JDBC)Query string concatenation

These run automatically - no additional configuration. Disable selectively via --disabled_hooks=....

Full CI job

- uses: actions/setup-java@v5
  with: { java-version: '17', distribution: 'temurin' }
- name: Run unit tests + regression fuzz inputs
  run: mvn test
- name: Smoke fuzz (3 min per target)
  run: |
    for cls in $(grep -rl "@FuzzTest" src/test/java/ | \
                 sed 's|src/test/java/||; s|/|.|g; s|.java||'); do
      JAZZER_FUZZ=180 mvn test -Dtest=$cls || true
    done
- uses: actions/upload-artifact@v4
  with:
    name: jazzer-crashes
    path: |
      crash-*
      src/test/resources/**/*

|| true is continue-on-crash: a finding does not fail the job, so the loop still fuzzes every target - triage findings from the uploaded jazzer-crashes artifact. To hard-fail the build on new findings instead, drop || true (the first crash then fails the step).

SKILL.md

tile.json