LocalJazzerRunner.java
/*
* Copyright 2017-2025 original authors
*
* 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
*
* https://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 io.micronaut.fuzzing.runner;
import com.code_intelligence.jazzer.Jazzer;
import com.code_intelligence.jazzer.agent.AgentInstaller;
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.code_intelligence.jazzer.driver.FuzzedDataProviderImpl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micronaut.core.annotation.NonNull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
/**
* This class can be used as a convenient runner for {@link io.micronaut.fuzzing.FuzzTarget}s. For
* example:
*
* <pre>{@code
* @FuzzTarget
* public class Example {
* public static void fuzzerTestOneInput(byte[] input) {
* ...
* }
*
* public static void main(String[] args) {
* LocalJazzerRunner.create(Example.class).fuzz();
* }
* }
* }</pre>
*
* Running the main method will run the fuzzer.
*/
public final class LocalJazzerRunner {
private static final ObjectMapper MAPPER = new ObjectMapper();
private final Class<?> targetClass;
private final DefinedFuzzTarget target;
private LocalJazzerRunner(Class<?> targetClass, DefinedFuzzTarget target) {
this.targetClass = targetClass;
this.target = target;
}
/**
* Create a runner for the given target class.
*
* @param fuzzTarget The fuzz target class. Must be annotated with {@link io.micronaut.fuzzing.FuzzTarget}.
* @return The runner
*/
@NonNull
public static LocalJazzerRunner create(@NonNull Class<?> fuzzTarget) {
return new LocalJazzerRunner(fuzzTarget, findDefinition(fuzzTarget, fuzzTarget));
}
private void writeDictionary(OutputStream out) throws IOException {
target.writeStaticDictionary(out);
if (target.dictionaryResources() != null) {
for (String r : target.dictionaryResources()) {
Enumeration<URL> urls = LocalJazzerRunner.class.getClassLoader().getResources(r);
if (!urls.hasMoreElements()) {
throw new IllegalStateException("Dictionary resource " + r + " not found");
}
do {
DefinedFuzzTarget.writeResourceDictionaryPrefix(out, r);
try (InputStream in = urls.nextElement().openStream()) {
in.transferTo(out);
}
out.write('\n');
} while (urls.hasMoreElements());
}
}
}
/**
* Run the normal jazzer fuzzer.
*/
public void fuzz() {
Path dict = null;
try {
List<String> args = new ArrayList<>();
if (target.dictionaryResources() != null || target.dictionary() != null) {
dict = Files.createTempFile("fuzzing-", ".dict");
try (OutputStream out = Files.newOutputStream(dict)) {
writeDictionary(out);
}
args.add("-dict=" + dict.toAbsolutePath());
}
args.add("--target_class=" + target.targetClass());
Jazzer.main(args.toArray(new String[0]));
} catch (IOException e) {
throw new UncheckedIOException(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (dict != null) {
try {
Files.deleteIfExists(dict);
} catch (IOException ignored) {
}
}
}
}
/**
* Reproduce a crash with the given data in this JVM, for easy debugging.
*
* @param path Path to the data that leads to the crash
* @see #reproduce(byte[])
*/
public void reproduce(@NonNull Path path) {
try {
reproduce(Files.readAllBytes(path));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/**
* Reproduce a crash with the given data in this JVM, for easy debugging.
*
* @param data The data that leads to the crash
*/
public void reproduce(byte @NonNull [] data) {
// this method somewhat based on jazzer's Replayer.java
ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true);
// this is needed to get some bootstrap classes (UnsafeProvider), but we don't actually need to instrument anything
AgentInstaller.install(false);
try {
try {
MethodHandles.lookup().findStatic(targetClass, "fuzzerInitialize", MethodType.methodType(void.class))
.invoke();
} catch (NoSuchMethodException ignored) {
}
try {
MethodHandles.lookup().findStatic(targetClass, "fuzzerTestOneInput", MethodType.methodType(void.class, byte[].class))
.invokeExact(data);
} catch (NoSuchMethodException e) {
try {
MethodHandles.lookup().findStatic(targetClass, "fuzzerTestOneInput", MethodType.methodType(void.class, FuzzedDataProvider.class))
.invokeExact((FuzzedDataProvider) FuzzedDataProviderImpl.withJavaData(data));
} catch (NoSuchMethodException f) {
throw new IllegalArgumentException("Found no fuzzerTestOneInput method with appropriate argument type on " + targetClass, f);
}
}
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
private static DefinedFuzzTarget findDefinition(@NonNull Class<?> ctx, @NonNull Class<?> fuzzTarget) {
try (InputStream stream = LocalJazzerRunner.class.getResourceAsStream("/META-INF/" + DefinedFuzzTarget.DIRECTORY + "/" + ctx.getName() + ".json")) {
if (stream == null) {
if (ctx.getEnclosingClass() == null) {
throw new IllegalArgumentException("No fuzz target metadata found for " + fuzzTarget.getName() + ". Please make sure the target is annotated with @FuzzTarget, and that the annotation processor is applied.");
}
return findDefinition(ctx.getEnclosingClass(), fuzzTarget);
}
List<DefinedFuzzTarget> available = MAPPER.readValue(stream, new TypeReference<>() {
});
for (DefinedFuzzTarget target : available) {
if (target.targetClass().equals(fuzzTarget.getName())) {
return target;
}
}
throw new IllegalArgumentException("No fuzz target metadata found for " + fuzzTarget.getName() + ", but other metadata is present. Please make sure the target is annotated with @FuzzTarget.");
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}