Skip to content

Commit

Permalink
Add module for local jazzer runs
Browse files Browse the repository at this point in the history
  • Loading branch information
yawkat committed Jan 7, 2025
1 parent 940f0be commit 5c5f864
Show file tree
Hide file tree
Showing 13 changed files with 293 additions and 43 deletions.
5 changes: 5 additions & 0 deletions build-logic/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ plugins {
id 'groovy-gradle-plugin'
}

repositories {
mavenCentral()
}

dependencies {
implementation(gradleApi())
compileOnly("org.jetbrains:annotations:26.0.1")
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.TaskAction;
import org.intellij.lang.annotations.Language;

import java.io.IOException;
import java.io.PrintWriter;
Expand All @@ -43,18 +44,57 @@ public void generateModelClasses() throws IOException {
try (var writer = new PrintWriter(Files.newBufferedWriter(model))) {
writer.println("package " + packageName + ";");
writer.println();
writer.println("""
import java.util.List;
public record DefinedFuzzTarget(
String targetClass,
List<String> dictionary,
List<String> dictionaryResources,
boolean enableImplicitly
) {
public static final String DIRECTORY = "io.micronaut.fuzzing.fuzz-targets";
@Language("java")
String s = """
import java.io.OutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import io.micronaut.core.annotation.Internal;
@Internal
public record DefinedFuzzTarget(
String targetClass,
List<String> dictionary,
List<String> dictionaryResources,
boolean enableImplicitly
) {
public static final String DIRECTORY = "io.micronaut.fuzzing.fuzz-targets";
public void writeStaticDictionary(OutputStream out) throws IOException {
if (dictionary() != null) {
out.write("# Manually defined dictionary entries\\n".getBytes(StandardCharsets.UTF_8));
for (String s : dictionary()) {
out.write('"');
for (byte b : s.getBytes(StandardCharsets.UTF_8)) {
if (b == '"' || b == '\\\\') {
// escape \\ and "
out.write('\\\\');
out.write(b);
} else if (b >= ' ' && b <= '~') {
// printable ascii char
out.write((char) b);
} else {
out.write('\\\\');
out.write('x');
if ((b & 0xff) < 0x10) {
out.write('0');
}
out.write(Integer.toHexString(b & 0xff).getBytes(StandardCharsets.UTF_8));
}
}
""");
out.write('"');
out.write('\\n');
}
}
}
public static void writeResourceDictionaryPrefix(OutputStream out, String resourceName) throws IOException {
out.write(("# Dictionary from " + resourceName + "\\n").getBytes(StandardCharsets.UTF_8));
}
}
""";
writer.println(s.stripIndent());
}
}
}
2 changes: 0 additions & 2 deletions fuzzing-annotation-processor/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import io.micronaut.build.internal.tasks.GenerateModelClasses

plugins {
id("io.micronaut.build.internal.fuzzing-module")
id("io.micronaut.build.internal.fuzzing-model")
Expand Down
16 changes: 16 additions & 0 deletions fuzzing-runner/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
id("io.micronaut.build.internal.fuzzing-module")
id("io.micronaut.build.internal.fuzzing-model")
}

dependencies {
implementation(libs.managed.jazzer.standalone)
implementation(mn.jackson.databind)
implementation(projects.micronautFuzzingApi)
}

tasks {
generateModel {
packageName = "io.micronaut.fuzzing.runner"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/*
* 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);
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);
}
}
}
1 change: 1 addition & 0 deletions fuzzing-tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies {
implementation(mnLogging.logback.classic)

implementation(projects.micronautFuzzingApi)
implementation(projects.micronautFuzzingRunner)

annotationProcessor(mn.micronaut.inject.java)
annotationProcessor(projects.micronautFuzzingAnnotationProcessor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.micronaut.fuzzing;

import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import io.micronaut.fuzzing.runner.LocalJazzerRunner;

@FuzzTarget(enableImplicitly = false)
public class TestTarget {
Expand All @@ -29,4 +30,8 @@ public static void fuzzerTestOneInput(FuzzedDataProvider provider) {
}
}
}

public static void main(String[] args) {
LocalJazzerRunner.create(TestTarget.class).fuzz();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.micronaut.fuzzing.FlagAppender;
import io.micronaut.fuzzing.FuzzTarget;
import io.micronaut.fuzzing.HttpDict;
import io.micronaut.fuzzing.runner.LocalJazzerRunner;
import io.micronaut.http.server.netty.NettyHttpServer;
import io.micronaut.runtime.server.EmbeddedServer;
import io.netty.buffer.ByteBuf;
Expand Down Expand Up @@ -86,4 +87,8 @@ public static void fuzzerTestOneInput(byte[] input) {
public static void fuzzerTearDown() {
//CustomResourceLeakDetector.reportStillOpen();
}

public static void main(String[] args) {
LocalJazzerRunner.create(EmbeddedHttpTarget.class).fuzz();
}
}
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ micronaut-logging = { module = "io.micronaut.logging:micronaut-logging-bom", ver
# jdoctor = { module = "me.champeau.jdoctor:jdoctor-core", version.ref="jdoctor" }

managed-jazzer-api = { module = 'com.code-intelligence:jazzer-api', version.ref = 'managed-jazzer' }
managed-jazzer-standalone = { module = 'com.code-intelligence:jazzer', version.ref = 'managed-jazzer' }

[bundles]

Expand Down
3 changes: 2 additions & 1 deletion jazzer-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ repositories {

dependencies {
implementation(mn.jackson.databind)
compileOnly(mn.micronaut.core) // annotations

testImplementation(mnTest.junit.jupiter.api)
testImplementation(mnTest.junit.jupiter.engine)
}

tasks{
tasks {
test {
useJUnitPlatform()
}
Expand Down
Loading

0 comments on commit 5c5f864

Please sign in to comment.