Skip to content

Commit

Permalink
* Added support for Jazzer's fuzzerInitialize and fuzzerTearDown call…
Browse files Browse the repository at this point in the history
…backs

* Fixed defect where AnalysisReplayerManager#next could return null
* Changed CampaignAnalyzer#closeConnection to more gracefully terminate the forked analysis process
* Changed from using old Jazzer replayer to using Jazzer's FuzzTarget and LifecycleMethodsInvoker to perform analysis reruns
  • Loading branch information
katherine-hough committed Oct 27, 2023
1 parent 29f4345 commit 22bd28b
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,37 @@
public final class AnalysisReplayerManager implements Closeable, ReplayerManager {
private final ForkConnection connection;
private final StackTraceCleaner cleaner;
private File nextInput = null;

public AnalysisReplayerManager(int port, int maxTraceSize) throws IOException {
this.connection = new ForkConnection(port);
this.cleaner = new StackTraceCleaner(maxTraceSize);
}

@Override
public File nextInput() throws IOException {
File input;
try {
input = connection.receive(File.class);
} catch (ClassNotFoundException e) {
throw new AssertionError(e);
public File nextInput() {
if (!hasNextInput()) {
throw new IllegalStateException();
}
// Reset the JaCoCo coverage
RT.getAgent().reset();
return input;
File temp = nextInput;
nextInput = null;
return temp;
}

@Override
public boolean hasNextInput() {
return !connection.isClosed();
if (nextInput == null) {
try {
nextInput = connection.receive(File.class);
} catch (ClassNotFoundException e) {
throw new AssertionError(e);
} catch (IOException e) {
return false;
}
}
return nextInput != null;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
public interface ReplayerManager extends Closeable {
File nextInput() throws IOException;

boolean hasNextInput();
boolean hasNextInput() throws IOException;

void handleResult(Throwable failure) throws IOException;
}
4 changes: 2 additions & 2 deletions meringue-jazzer-extension/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
</dependency>
<dependency>
<groupId>com.code-intelligence</groupId>
<artifactId>jazzer-replayer</artifactId>
<version>0.10.0</version>
<artifactId>jazzer</artifactId>
<version>${jazzer.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.surefire</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package edu.neu.ccs.prl.meringue;

import com.code_intelligence.jazzer.driver.FuzzTargetHolder;
import com.code_intelligence.jazzer.driver.FuzzedDataProviderImpl;
import com.code_intelligence.jazzer.driver.LifecycleMethodsInvoker;
import com.code_intelligence.jazzer.utils.Log;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;

public final class FuzzTargetRunner {
private final MethodHandle fuzzTargetMethod;
private final LifecycleMethodsInvoker lifecycleMethodsInvoker;
private final boolean useFuzzedDataProvider;
private final Object fuzzTargetInstance;

public FuzzTargetRunner(String testClassName) throws Throwable {
FuzzTargetHolder.FuzzTarget fuzzTarget = findFuzzTarget(testClassName);
lifecycleMethodsInvoker = fuzzTarget.lifecycleMethodsInvoker;
fuzzTarget.method.setAccessible(true);
fuzzTargetMethod = MethodHandles.lookup().unreflect(fuzzTarget.method);
useFuzzedDataProvider = fuzzTarget.usesFuzzedDataProvider();
fuzzTargetInstance = fuzzTarget.newInstance.call();
lifecycleMethodsInvoker.beforeFirstExecution();
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
}

public Throwable run(byte[] data) {
try {
if (useFuzzedDataProvider) {
try (FuzzedDataProviderImpl provider = FuzzedDataProviderImpl.withJavaData(data)) {
runInternal(provider);
}
} else {
runInternal(data);
}
} catch (Throwable t) {
return t;
}
return null;
}

public void runInternal(Object argument) throws Throwable {
lifecycleMethodsInvoker.beforeEachExecution();
if (fuzzTargetInstance == null) {
fuzzTargetMethod.invoke(argument);
} else {
fuzzTargetMethod.invoke(fuzzTargetInstance, argument);
}
}

private void shutdown() {
try {
lifecycleMethodsInvoker.afterLastExecution();
} catch (Throwable t) {
Log.finding(t);
}
}

private FuzzTargetHolder.FuzzTarget findFuzzTarget(String testClassName) throws ReflectiveOperationException {
Class<?> clazz = Class.forName("com.code_intelligence.jazzer.driver.FuzzTargetFinder");
Method m = clazz.getDeclaredMethod("findFuzzTarget", String.class);
m.setAccessible(true);
return (FuzzTargetHolder.FuzzTarget) m.invoke(null, testClassName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public final class JazzerFramework implements FuzzFramework {
private File reproducerDir;
private File workingDir;
private File logFile;
private File jazzerExecutable;
private File jazzerBootstrapJar;
private boolean quiet = false;
private ProcessBuilder builder;

Expand All @@ -26,13 +28,23 @@ public void initialize(CampaignConfiguration config, Properties frameworkArgumen
workingDir = new File(outputDir, "out");
logFile = new File(outputDir, "jazzer.log");
quiet = Boolean.parseBoolean(frameworkArguments.getProperty("quiet", "false"));
List<String> command = createCommand(config, frameworkArguments, outputDir, reproducerDir, corpusDir);
File jazzerExec = getJazzerResource(outputDir, "jazzer");
if (!jazzerExec.setExecutable(true)) {
throw new IllegalStateException("Failed to assign executable permissions to Jazzer executable");
}
jazzerExecutable = jazzerExec;
getJazzerResource(outputDir, "jazzer_standalone.jar");
String resourcePath = File.separator + String.join(File.separator, "com", "code_intelligence",
"jazzer", "runtime", "jazzer_bootstrap.jar");
jazzerBootstrapJar = getJazzerResource(outputDir, resourcePath, "jazzer_bootstrap.jar");
List<String> command = createCommand(config, frameworkArguments);
builder = new ProcessBuilder().command(command).directory(workingDir);
if (config.getEnvironment() != null) {
builder.environment().clear();
builder.environment().putAll(config.getEnvironment());
}
builder.environment().put("JAVA_HOME", FileUtil.javaExecToJavaHome(config.getJavaExecutable()).getAbsolutePath());
builder.environment().put("JAVA_HOME", FileUtil.javaExecToJavaHome(config.getJavaExecutable())
.getAbsolutePath());
}

@Override
Expand All @@ -46,7 +58,7 @@ public Process startCampaign() throws IOException {
return ProcessUtil.start(builder, true);
} else {
return builder.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile))
.redirectError(ProcessBuilder.Redirect.appendTo(logFile)).start();
.redirectError(ProcessBuilder.Redirect.appendTo(logFile)).start();
}
}

Expand All @@ -58,7 +70,7 @@ public File[] getCorpusFiles() {
@Override
public File[] getFailureFiles() {
return Arrays.stream(Objects.requireNonNull(workingDir.listFiles()))
.filter(f -> f.getName().startsWith("crash-")).toArray(File[]::new);
.filter(f -> f.getName().startsWith("crash-")).toArray(File[]::new);
}

@Override
Expand All @@ -68,7 +80,7 @@ public Class<? extends Replayer> getReplayerClass() {

@Override
public Collection<File> getRequiredClassPathElements() {
return Collections.singleton(FileUtil.getClassPathElement(JazzerFramework.class));
return Arrays.asList(FileUtil.getClassPathElement(JazzerFramework.class), jazzerBootstrapJar);
}

@Override
Expand All @@ -86,14 +98,13 @@ public Process restartCampaign() throws IOException {
return ProcessUtil.start(builder, true);
} else {
return builder.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile))
.redirectError(ProcessBuilder.Redirect.appendTo(logFile)).start();
.redirectError(ProcessBuilder.Redirect.appendTo(logFile)).start();
}
}

private static List<String> createCommand(CampaignConfiguration config, Properties frameworkArguments,
File outputDir, File reproducerDir, File corpusDir) throws IOException {
private List<String> createCommand(CampaignConfiguration config, Properties frameworkArguments) {
List<String> command = new LinkedList<>();
command.add(getJazzerExecutable(outputDir).getAbsolutePath());
command.add(jazzerExecutable.getAbsolutePath());
String classPath = config.getTestClasspathJar().getAbsolutePath() + File.pathSeparator +
FileUtil.getClassPathElement(JazzerFramework.class).getAbsolutePath();
command.add("--cp=" + classPath);
Expand Down Expand Up @@ -121,8 +132,7 @@ private static List<String> createCommand(CampaignConfiguration config, Properti
return command;
}

private static File getJazzerExecutable(File outputDir) throws IOException {
String executableName = "jazzer";
private static File getJazzerResource(File outputDir, String resourceName) throws IOException {
String resourcePathPrefix;
if (SystemUtils.IS_OS_MAC) {
resourcePathPrefix = "mac";
Expand All @@ -131,23 +141,22 @@ private static File getJazzerExecutable(File outputDir) throws IOException {
} else {
throw new IllegalStateException("Operating system not supported");
}
String[] resourceNames = new String[]{"jazzer_standalone.jar", executableName};
File bin = new File(outputDir, "bin");
FileUtil.ensureDirectory(bin);
File jazzerExec = new File(bin, executableName);
for (String resourceName : resourceNames) {
String resourcePath = resourcePathPrefix + File.separator + resourceName;
try (InputStream in = JazzerFramework.class.getResourceAsStream(resourcePath)) {
if (in == null) {
throw new IllegalStateException("Unable to locate Jazzer resource: " + resourcePath);
}
File out = new File(bin, resourceName);
Files.copy(in, out.toPath(), StandardCopyOption.REPLACE_EXISTING);
String resourcePath = resourcePathPrefix + File.separator + resourceName;
return getJazzerResource(outputDir, resourcePath, resourceName);
}

private static File getJazzerResource(File outputDir, String resourcePath, String resourceName) throws IOException {
File bin = new File(outputDir, "bin");
FileUtil.ensureDirectory(bin);
try (InputStream in = JazzerFramework.class.getResourceAsStream(resourcePath)) {
if (in == null) {
throw new IllegalStateException("Unable to locate Jazzer resource: " + resourcePath);
}
File out = new File(bin, resourceName);
Files.copy(in, out.toPath(), StandardCopyOption.REPLACE_EXISTING);
return out;
}
if (!jazzerExec.setExecutable(true)) {
throw new IllegalStateException("Failed to assign executable permissions to Jazzer executable");
}
return jazzerExec;
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
package edu.neu.ccs.prl.meringue;

import com.code_intelligence.jazzer.replay.Replayer;
import com.code_intelligence.jazzer.driver.Opt;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;

public final class JazzerReplayer implements edu.neu.ccs.prl.meringue.Replayer {
private FuzzTargetRunner runner;

@Override
public void configure(String testClassName, String testMethodName, ClassLoader classLoader) {
JazzerTargetWrapper.fuzzerInitialize(new String[]{testClassName, testMethodName});
JazzerTargetWrapper.setRethrow(false);
public void configure(String testClassName, String testMethodName, ClassLoader classLoader) throws Throwable {
if ("fuzzerTestOneInput".equals(testMethodName)) {
runner = new FuzzTargetRunner(testClassName);
} else {
Opt.targetArgs.setIfDefault(Arrays.asList(testClassName, testMethodName));
runner = new FuzzTargetRunner(JazzerTargetWrapper.class.getName());
}
}

private Throwable execute(File input) throws IOException {
Replayer.executeFuzzTarget(JazzerTargetWrapper.class, Files.readAllBytes(input.toPath()));
return JazzerTargetWrapper.getLastThrown();
return runner.run(Files.readAllBytes(input.toPath()));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,17 @@
import java.lang.reflect.InvocationTargetException;

public final class JazzerTargetWrapper {
private static Throwable lastThrown = null;
private static JazzerTarget target;
private static boolean rethrow = true;

private JazzerTargetWrapper() {
throw new AssertionError(getClass().getSimpleName() + " is a static utility class and should " +
"not be instantiated");
}

public static void fuzzerInitialize(String[] args) {
try {
target = new JazzerTarget(args[0], args[1], JazzerTargetWrapper.class.getClassLoader());
} catch (Throwable t) {
t.printStackTrace();
System.exit(-1);
}
target = new JazzerTarget(args[0], args[1], JazzerTargetWrapper.class.getClassLoader());
}

public static void fuzzerTestOneInput(FuzzedDataProvider provider) throws Throwable {
lastThrown = null;
try {
target.execute(provider);
} catch (InvocationTargetException t) {
lastThrown = t.getTargetException();
if (rethrow) {
throw lastThrown;
}
throw t.getTargetException();
}
}

public static void setRethrow(boolean value) {
rethrow = value;
}

public static Throwable getLastThrown() {
return lastThrown;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
==============================================================
Jazzer
This software release contains components from Jazzer (versions 0.21.1 and 0.10.0).
This software release contains components from Jazzer (version 0.21.1)).
Jazzer is available at: https://github.com/CodeIntelligenceTesting/jazzer.

Jazzer is licensed under the following terms:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,8 @@ private void closeConnection() {
connection = null;
}
if (process != null && process.isAlive()) {
process.destroyForcibly();
try {
process.waitFor();
ProcessUtil.stop(process);
} catch (InterruptedException e) {
//
}
Expand Down

0 comments on commit 22bd28b

Please sign in to comment.