From 22bd28b643bdd7318c9f2c23d120052e090cfd99 Mon Sep 17 00:00:00 2001 From: katherine-hough <32645020+katherine-hough@users.noreply.github.com> Date: Fri, 27 Oct 2023 14:32:30 -0400 Subject: [PATCH] * Added support for Jazzer's fuzzerInitialize and fuzzerTearDown callbacks * 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 --- .../prl/meringue/AnalysisReplayerManager.java | 25 ++++--- .../neu/ccs/prl/meringue/ReplayerManager.java | 2 +- meringue-jazzer-extension/pom.xml | 4 +- .../ccs/prl/meringue/FuzzTargetRunner.java | 67 +++++++++++++++++++ .../neu/ccs/prl/meringue/JazzerFramework.java | 59 +++++++++------- .../neu/ccs/prl/meringue/JazzerReplayer.java | 18 +++-- .../ccs/prl/meringue/JazzerTargetWrapper.java | 28 +------- .../src/main/resources/META-INF/NOTICE.txt | 2 +- .../ccs/prl/meringue/CampaignAnalyzer.java | 3 +- 9 files changed, 137 insertions(+), 71 deletions(-) create mode 100644 meringue-jazzer-extension/src/main/java/edu/neu/ccs/prl/meringue/FuzzTargetRunner.java diff --git a/meringue-core/src/main/java/edu/neu/ccs/prl/meringue/AnalysisReplayerManager.java b/meringue-core/src/main/java/edu/neu/ccs/prl/meringue/AnalysisReplayerManager.java index 6f5fb6a..b93179b 100644 --- a/meringue-core/src/main/java/edu/neu/ccs/prl/meringue/AnalysisReplayerManager.java +++ b/meringue-core/src/main/java/edu/neu/ccs/prl/meringue/AnalysisReplayerManager.java @@ -9,6 +9,7 @@ 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); @@ -16,21 +17,29 @@ public AnalysisReplayerManager(int port, int maxTraceSize) throws IOException { } @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 diff --git a/meringue-core/src/main/java/edu/neu/ccs/prl/meringue/ReplayerManager.java b/meringue-core/src/main/java/edu/neu/ccs/prl/meringue/ReplayerManager.java index 33c9e3c..8c58abf 100644 --- a/meringue-core/src/main/java/edu/neu/ccs/prl/meringue/ReplayerManager.java +++ b/meringue-core/src/main/java/edu/neu/ccs/prl/meringue/ReplayerManager.java @@ -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; } diff --git a/meringue-jazzer-extension/pom.xml b/meringue-jazzer-extension/pom.xml index 0bd0291..1ad9e6f 100644 --- a/meringue-jazzer-extension/pom.xml +++ b/meringue-jazzer-extension/pom.xml @@ -27,8 +27,8 @@ com.code-intelligence - jazzer-replayer - 0.10.0 + jazzer + ${jazzer.version} org.apache.maven.surefire diff --git a/meringue-jazzer-extension/src/main/java/edu/neu/ccs/prl/meringue/FuzzTargetRunner.java b/meringue-jazzer-extension/src/main/java/edu/neu/ccs/prl/meringue/FuzzTargetRunner.java new file mode 100644 index 0000000..3c36b83 --- /dev/null +++ b/meringue-jazzer-extension/src/main/java/edu/neu/ccs/prl/meringue/FuzzTargetRunner.java @@ -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); + } +} diff --git a/meringue-jazzer-extension/src/main/java/edu/neu/ccs/prl/meringue/JazzerFramework.java b/meringue-jazzer-extension/src/main/java/edu/neu/ccs/prl/meringue/JazzerFramework.java index d3a3a93..ba2f073 100644 --- a/meringue-jazzer-extension/src/main/java/edu/neu/ccs/prl/meringue/JazzerFramework.java +++ b/meringue-jazzer-extension/src/main/java/edu/neu/ccs/prl/meringue/JazzerFramework.java @@ -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; @@ -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 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 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 @@ -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(); } } @@ -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 @@ -68,7 +80,7 @@ public Class getReplayerClass() { @Override public Collection getRequiredClassPathElements() { - return Collections.singleton(FileUtil.getClassPathElement(JazzerFramework.class)); + return Arrays.asList(FileUtil.getClassPathElement(JazzerFramework.class), jazzerBootstrapJar); } @Override @@ -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 createCommand(CampaignConfiguration config, Properties frameworkArguments, - File outputDir, File reproducerDir, File corpusDir) throws IOException { + private List createCommand(CampaignConfiguration config, Properties frameworkArguments) { List 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); @@ -121,8 +132,7 @@ private static List 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"; @@ -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; } } diff --git a/meringue-jazzer-extension/src/main/java/edu/neu/ccs/prl/meringue/JazzerReplayer.java b/meringue-jazzer-extension/src/main/java/edu/neu/ccs/prl/meringue/JazzerReplayer.java index 49716d1..4a0423b 100644 --- a/meringue-jazzer-extension/src/main/java/edu/neu/ccs/prl/meringue/JazzerReplayer.java +++ b/meringue-jazzer-extension/src/main/java/edu/neu/ccs/prl/meringue/JazzerReplayer.java @@ -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 diff --git a/meringue-jazzer-extension/src/main/java/edu/neu/ccs/prl/meringue/JazzerTargetWrapper.java b/meringue-jazzer-extension/src/main/java/edu/neu/ccs/prl/meringue/JazzerTargetWrapper.java index 7159e88..a9279e6 100644 --- a/meringue-jazzer-extension/src/main/java/edu/neu/ccs/prl/meringue/JazzerTargetWrapper.java +++ b/meringue-jazzer-extension/src/main/java/edu/neu/ccs/prl/meringue/JazzerTargetWrapper.java @@ -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; - } } diff --git a/meringue-jazzer-extension/src/main/resources/META-INF/NOTICE.txt b/meringue-jazzer-extension/src/main/resources/META-INF/NOTICE.txt index ee50951..f14292b 100644 --- a/meringue-jazzer-extension/src/main/resources/META-INF/NOTICE.txt +++ b/meringue-jazzer-extension/src/main/resources/META-INF/NOTICE.txt @@ -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: diff --git a/meringue-maven-plugin/src/main/java/edu/neu/ccs/prl/meringue/CampaignAnalyzer.java b/meringue-maven-plugin/src/main/java/edu/neu/ccs/prl/meringue/CampaignAnalyzer.java index 3f42e27..ce26824 100644 --- a/meringue-maven-plugin/src/main/java/edu/neu/ccs/prl/meringue/CampaignAnalyzer.java +++ b/meringue-maven-plugin/src/main/java/edu/neu/ccs/prl/meringue/CampaignAnalyzer.java @@ -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) { // }