Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion docs/common.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
* [Autofuzz Mode](#autofuzz-mode)

> [!NOTE]\
> These settings apply to the old fuzzing approach using a `fuzzerTestOneInput` method and the native Jazzer binary. They don't work in the new JUnit integration.
> This page focuses on standalone/CLI usage. The JUnit integration uses the same precedence model, however some options
> are not used when executing fuzz tests via the JUnit integration.

## Recommended JVM Options

Expand All @@ -34,6 +35,15 @@ The value of a setting item `some_opt` is obtained from the following sources in
- the `jazzer.some_opt` JUnit configuration parameter
- the `--some_opt` CLI parameter

## Global Time/Execution Limits

- `jazzer.max_duration`: Sets the maximum fuzzing duration (e.g., `30s`, `2m`, `1h`; empty = unlimited).
- Standalone: translated to libFuzzer flag `-max_total_time` unless that flag is already set.
- JUnit: overrides `@FuzzTest(maxDuration)`.
- `jazzer.max_executions`: Sets the maximum number of executions (positive value limits runs).
- Standalone: translated to libFuzzer flag `-runs` unless that flag is already set.
- JUnit: overrides `@FuzzTest(maxExecutions)`.

### Reproducing a finding

When Jazzer manages to find an input that causes an uncaught exception or a failed assertion, it prints a Java stack trace and creates two files that aid in reproducing the crash without Jazzer:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ java_library(
"//src/main/java/com/code_intelligence/jazzer/android:android_runtime",
"//src/main/java/com/code_intelligence/jazzer/driver/junit:junit_runner",
"//src/main/java/com/code_intelligence/jazzer/runtime:constants",
"//src/main/java/com/code_intelligence/jazzer/utils",
"//src/main/java/com/code_intelligence/jazzer/utils:log",
],
)
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/code_intelligence/jazzer/driver/Driver.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.code_intelligence.jazzer.agent.AgentInstaller;
import com.code_intelligence.jazzer.driver.junit.JUnitRunner;
import com.code_intelligence.jazzer.utils.Log;
import com.code_intelligence.jazzer.utils.Utils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
Expand Down Expand Up @@ -159,6 +160,20 @@ public static int start(List<String> args, boolean spawnsSubprocesses) throws IO
}
}

// Translate Jazzer options to libFuzzer flags if not explicitly provided.
if (Opt.maxDuration.isSet() && !Opt.maxDuration.get().isEmpty()) {
boolean hasMaxTime = args.stream().anyMatch(a -> a.startsWith("-max_total_time="));
if (!hasMaxTime) {
args.add("-max_total_time=" + Utils.durationStringToSeconds(Opt.maxDuration.get()));
}
}
if (Opt.maxExecutions.isSet() && Opt.maxExecutions.get() > 0) {
boolean hasRuns = args.stream().anyMatch(a -> a.startsWith("-runs="));
if (!hasRuns) {
args.add("-runs=" + Opt.maxExecutions.get());
}
}

// Installing the agent after the following "findFuzzTarget" leads to an asan error
// in it on "Class.forName(targetClassName)", but only during native fuzzing.
AgentInstaller.install(Opt.hooks.get());
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/com/code_intelligence/jazzer/driver/Opt.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,20 @@ public final class Opt {
"",
"Fully qualified name of the fuzz target class (required unless --autofuzz is"
+ " specified)");
public static final OptItem<String> maxDuration =
stringSetting(
"max_duration",
"",
"Sets the maximum fuzzing duration (e.g., '30s', '2m', '1h'; empty = unlimited). For"
+ " JUnit tests, overrides @FuzzTest(maxDuration); for standalone, translates to"
+ " -max_total_time unless that flag is already present.");
public static final OptItem<Long> maxExecutions =
uint64Setting(
"max_executions",
0,
"Sets the maximum number of executions during fuzzing (positive value limits runs). For"
+ " JUnit tests, overrides @FuzzTest(maxExecutions); for standalone, translates to"
+ " -runs unless that flag is already present.");
// Used to disambiguate between multiple methods annotated with @FuzzTest in the target class.
public static final OptItem<String> targetMethod =
stringSetting(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,19 @@ static void forRegressionTest(ExtensionContext extensionContext) {
Opt.customHookIncludes.setIfDefault(Opt.instrument.get());
}

static void forFuzzing(ExtensionContext extensionContext) {
static void forFuzzing(
ExtensionContext extensionContext, String fuzzingDuration, long maxExecutions) {
if (!hasBeenConfigured.compareAndSet(false, true)) {
throw new IllegalStateException("Only a single fuzz test should be executed per fuzzing run");
}

applyCommonConfiguration(extensionContext);

// Allow dynamic overrides via Opt while keeping annotation values as defaults.
// If the user supplied jazzer.max_duration/JAZZER_MAX_DURATION/--max_duration, it will win.
Opt.maxDuration.setIfDefault(fuzzingDuration);
Opt.maxExecutions.setIfDefault(maxExecutions);

Opt.instrument.setIfDefault(determineInstrumentationFilters(extensionContext));
Opt.customHookIncludes.setIfDefault(Opt.instrument.get());
Opt.instrumentationIncludes.setIfDefault(Opt.instrument.get());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ java_jni_library(
":utils",
"//deploy:jazzer",
"//deploy:jazzer-api",
"//src/main/java/com/code_intelligence/jazzer/utils",
"@maven//:jakarta_servlet_jakarta_servlet_api",
"@maven//:org_junit_jupiter_junit_jupiter_api",
"@maven//:org_junit_jupiter_junit_jupiter_params",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@
* <p>To remove the default limit of 5 minutes, set this element to {@code ""}.
*
* <p>This option has no effect during regression testing.
*
* <p>This value can be overridden at runtime via the Jazzer option {@code jazzer.max_duration}.
*/
String maxDuration() default "5m";

Expand All @@ -124,6 +126,8 @@
* fuzzing across machine's with different performance characteristics.
*
* <p>This option has no effect during regression testing.
*
* <p>This value can be overridden at runtime via the Jazzer option {@code jazzer.max_executions}.
*/
long maxExecutions() default 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
package com.code_intelligence.jazzer.junit;

import static com.code_intelligence.jazzer.junit.ApiStatsHolder.printApiStats;
import static com.code_intelligence.jazzer.junit.Utils.durationStringToSeconds;
import static com.code_intelligence.jazzer.junit.Utils.generatedCorpusPath;
import static com.code_intelligence.jazzer.junit.Utils.inputsDirectoryResourcePath;
import static com.code_intelligence.jazzer.junit.Utils.inputsDirectorySourcePath;
import static com.code_intelligence.jazzer.utils.Utils.durationStringToSeconds;

import com.code_intelligence.jazzer.agent.AgentInstaller;
import com.code_intelligence.jazzer.driver.FuzzTargetHolder;
Expand Down Expand Up @@ -64,8 +64,7 @@ private FuzzTestExecutor(List<String> libFuzzerArgs, Optional<Path> javaSeedsDir
this.javaSeedsDir = javaSeedsDir;
}

public static FuzzTestExecutor prepare(
ExtensionContext context, String maxDuration, long maxRuns, Optional<Path> dictionaryPath)
public static FuzzTestExecutor prepare(ExtensionContext context, Optional<Path> dictionaryPath)
throws IOException {
if (!hasBeenPrepared.compareAndSet(false, true)) {
throw new FuzzTestConfigurationError(
Expand Down Expand Up @@ -116,9 +115,9 @@ public static FuzzTestExecutor prepare(

dictionaryPath.ifPresent(s -> libFuzzerArgs.add("-dict=" + s));

libFuzzerArgs.add("-max_total_time=" + durationStringToSeconds(maxDuration));
if (maxRuns > 0) {
libFuzzerArgs.add("-runs=" + maxRuns);
libFuzzerArgs.add("-max_total_time=" + durationStringToSeconds(Opt.maxDuration.get()));
if (Opt.maxExecutions.get() > 0) {
libFuzzerArgs.add("-runs=" + Opt.maxExecutions.get());
}
// Disable libFuzzer's out of memory detection: It is only useful for native library fuzzing,
// which we don't support without our native driver, and leads to false positives where it picks
Expand Down Expand Up @@ -266,10 +265,11 @@ static void configureAndInstallAgent(
return;
}
if (Utils.isFuzzing(extensionContext)) {
FuzzTestExecutor executor =
prepare(extensionContext, maxDuration, maxExecutions, dictionaryPath);
// Register configuration parameters and apply common config before reading Opt values
// inside prepare(...), so JUnit config (junit-platform.properties) can override defaults.
AgentConfigurator.forFuzzing(extensionContext, maxDuration, maxExecutions);
FuzzTestExecutor executor = prepare(extensionContext, dictionaryPath);
extensionContext.getRoot().getStore(Namespace.GLOBAL).put(FuzzTestExecutor.class, executor);
AgentConfigurator.forFuzzing(extensionContext);
} else {
AgentConfigurator.forRegressionTest(extensionContext);
}
Expand Down
14 changes: 0 additions & 14 deletions src/main/java/com/code_intelligence/jazzer/junit/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
Expand Down Expand Up @@ -277,19 +276,6 @@ static List<String> getCorpusFilesOrDirs(ExtensionContext context) {
.collect(toList());
}

/**
* Convert the string to ISO 8601 (https://en.wikipedia.org/wiki/ISO_8601#Durations). We do not
* allow for duration units longer than hours, so we can always prepend PT.
*/
static long durationStringToSeconds(String duration) {
if (duration.isEmpty()) {
return 0;
}
String isoDuration =
"PT" + duration.replace("sec", "s").replace("min", "m").replace("hr", "h").replace(" ", "");
return Duration.parse(isoDuration).getSeconds();
}

static long parseJUnitTimeoutValueToSeconds(String value) {
Matcher matcher = DURATION_PATTERN.matcher(value);
if (!matcher.matches()) {
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/com/code_intelligence/jazzer/utils/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package com.code_intelligence.jazzer.utils

import java.lang.reflect.Executable
import java.time.Duration

val Class<*>.readableDescriptor: String
get() =
Expand Down Expand Up @@ -47,3 +48,21 @@ val Executable.readableDescriptor: String
parameterTypes.joinToString(separator = ",", prefix = "(", postfix = ")") { parameterType ->
parameterType.readableDescriptor
}

/**
* Convert the string to ISO 8601 (https://en.wikipedia.org/wiki/ISO_8601#Durations). We do not
* allow for duration units longer than hours, so we can always prepend PT.
*/
fun durationStringToSeconds(duration: String): Long {
if (duration.isEmpty()) {
return 0
}
val isoDuration =
"PT" +
duration
.replace("sec", "s")
.replace("min", "m")
.replace("hr", "h")
.replace(" ", "")
return Duration.parse(isoDuration).seconds
}
13 changes: 0 additions & 13 deletions src/test/java/com/code_intelligence/jazzer/junit/UtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.code_intelligence.jazzer.junit;

import static com.code_intelligence.jazzer.junit.Utils.durationStringToSeconds;
import static com.code_intelligence.jazzer.junit.Utils.getMarkedArguments;
import static com.code_intelligence.jazzer.junit.Utils.getMarkedInstance;
import static com.code_intelligence.jazzer.junit.Utils.isMarkedInstance;
Expand Down Expand Up @@ -58,18 +57,6 @@
public class UtilsTest implements InvocationInterceptor {
@TempDir Path temp;

@Test
void testDurationStringToSeconds() {
assertThat(durationStringToSeconds("")).isEqualTo(0);
assertThat(durationStringToSeconds("0s")).isEqualTo(0);
assertThat(durationStringToSeconds("1m")).isEqualTo(60);
assertThat(durationStringToSeconds("1min")).isEqualTo(60);
assertThat(durationStringToSeconds("1h")).isEqualTo(60 * 60);
assertThat(durationStringToSeconds("1h 2m 30s")).isEqualTo(60 * 60 + 2 * 60 + 30);
assertThat(durationStringToSeconds("1hr2min30sec")).isEqualTo(60 * 60 + 2 * 60 + 30);
assertThat(durationStringToSeconds("1h2m30s")).isEqualTo(60 * 60 + 2 * 60 + 30);
}

@Test
void testParseJUnitTimeoutValueToSeconds() {
assertThat(parseJUnitTimeoutValueToSeconds("5")).isEqualTo(5);
Expand Down
13 changes: 12 additions & 1 deletion src/test/java/com/code_intelligence/jazzer/utils/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("//bazel:compat.bzl", "SKIP_ON_WINDOWS")
load("@contrib_rules_jvm//java:defs.bzl", "JUNIT5_DEPS", "java_junit5_test")

java_library(
name = "test_utils",
Expand All @@ -8,3 +8,14 @@ java_library(
"//src/main/java/com/code_intelligence/jazzer/utils:log",
],
)

java_junit5_test(
name = "UtilsTest",
size = "small",
srcs = ["UtilsTest.java"],
deps = JUNIT5_DEPS + [
"//src/main/java/com/code_intelligence/jazzer/utils",
"@maven//:com_google_truth_truth",
"@maven//:org_junit_jupiter_junit_jupiter_api",
],
)
36 changes: 36 additions & 0 deletions src/test/java/com/code_intelligence/jazzer/utils/UtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2024 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.utils;

import static com.code_intelligence.jazzer.utils.Utils.durationStringToSeconds;
import static com.google.common.truth.Truth.assertThat;

import org.junit.jupiter.api.Test;

public class UtilsTest {
@Test
void testDurationStringToSeconds() {
assertThat(durationStringToSeconds("")).isEqualTo(0);
assertThat(durationStringToSeconds("0s")).isEqualTo(0);
assertThat(durationStringToSeconds("1m")).isEqualTo(60);
assertThat(durationStringToSeconds("1min")).isEqualTo(60);
assertThat(durationStringToSeconds("1h")).isEqualTo(60 * 60);
assertThat(durationStringToSeconds("1h 2m 30s")).isEqualTo(60 * 60 + 2 * 60 + 30);
assertThat(durationStringToSeconds("1hr2min30sec")).isEqualTo(60 * 60 + 2 * 60 + 30);
assertThat(durationStringToSeconds("1h2m30s")).isEqualTo(60 * 60 + 2 * 60 + 30);
}
}