// Copyright 2022 Code Intelligence GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.code_intelligence.jazzer.junit; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.parallel.ResourceAccessMode; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.api.parallel.Resources; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; /** * A parameterized test with parameters generated automatically by the Java fuzzer Jazzer. * *

Test parameters

* *

Methods annotated with {@link FuzzTest} can take either of the following types of parameters: * *

*
{@code byte[]}
*
Raw byte input mutated by the fuzzer. Use this signature when your fuzz test naturally * handles raw bytes (e.g. when fuzzing a binary format parser). This is the most efficient, but * also the least convenient way to write a fuzz test.
* *
{@link com.code_intelligence.jazzer.api.FuzzedDataProvider}
*
Provides convenience methods that generate instances of commonly used Java types from the raw * fuzzer input. This is generally the best way to write fuzz tests.
* *
any non-zero number of parameters of any type
*
In this case, Jazzer will rely on reflection and class path scanning to instantiate concrete * arguments. While convenient and a good way to get started, fuzz tests using this feature will * generally be less efficient than fuzz tests using any of the other possible signatures. Due to * the reliance on class path scanning, any change to the class path may also render previous * findings unreproducible.
*
* *

Test modes

* * A fuzz test can be run in two modes: fuzzing and regression testing. * *

Fuzzing

*

When the environment variable {@code JAZZER_FUZZ} is set to any non-empty value, fuzz tests * run in "fuzzing" mode. In this mode, the method annotated with {@link FuzzTest} are invoked * repeatedly with inputs that Jazzer generates and mutates based on feedback obtained from * instrumentation it applies to the test and every class loaded by it. * *

When an assertion in the test fails, an exception is thrown but not caught, or Jazzer's * instrumentation detects a security issue (e.g. SQL injection or insecure deserialization), the * fuzz test is reported as failed and the input is collected in the inputs directory for the test * class (see "Regression testing" for details). * *

When no issue has been found after the configured {@link FuzzTest#maxDuration()}, the test * passes. * *

Only a single fuzz test per test run will be executed in fuzzing mode. All other fuzz tests * will be skipped. * *

Regression testing

*

By default, a fuzz test is executed as a regular JUnit {@link ParameterizedTest} running on a * fixed set of inputs. It can be run together with regular unit tests and used to verify that past * findings remain fixed. In IDEs with JUnit 5 integration, it can also be used to conveniently * debug individual findings. * *

Fuzz tests are always executed on the empty input as well as all input files contained in the * resource directory called {@code Inputs} in the current package. For example, * all fuzz tests contained in the class {@code com.example.MyFuzzTests} would run on all files * under {@code src/test/resources/com/example/MyFuzzTestsInputs}. */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @AgentConfiguringArgumentsProviderArgumentsSource @ArgumentsSource(SeedArgumentsProvider.class) @FuzzingArgumentsProviderArgumentsSource @ExtendWith(FuzzTestExtensions.class) // {0} is expanded to the basename of the seed by the ArgumentProvider. @ParameterizedTest(name = "{0}") @Tag("jazzer") // Fuzz tests can't run in parallel with other fuzz tests since the last finding is kept in a global // variable. // Fuzz tests also can't run in parallel with other non-fuzz tests since method hooks are enabled // conditionally based on a global variable. @ResourceLock(value = Resources.GLOBAL, mode = ResourceAccessMode.READ_WRITE) public @interface FuzzTest { /** * A duration string such as "1h 2m 30s" indicating for how long the fuzz test should be executed * during fuzzing. * *

This option has no effect during regression testing. */ String maxDuration() default "5m"; } // Internal use only. // These wrappers are needed only because the container annotation for @ArgumentsSource, // @ArgumentsSources, can't be applied to annotations. @Target({ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @ArgumentsSource(AgentConfiguringArgumentsProvider.class) @interface AgentConfiguringArgumentsProviderArgumentsSource {} @Target({ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @ArgumentsSource(FuzzingArgumentsProvider.class) @interface FuzzingArgumentsProviderArgumentsSource {}