aboutsummaryrefslogtreecommitdiff
path: root/driver/src/test/java/com/code_intelligence/jazzer/driver/FuzzTargetRunnerTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'driver/src/test/java/com/code_intelligence/jazzer/driver/FuzzTargetRunnerTest.java')
-rw-r--r--driver/src/test/java/com/code_intelligence/jazzer/driver/FuzzTargetRunnerTest.java207
1 files changed, 207 insertions, 0 deletions
diff --git a/driver/src/test/java/com/code_intelligence/jazzer/driver/FuzzTargetRunnerTest.java b/driver/src/test/java/com/code_intelligence/jazzer/driver/FuzzTargetRunnerTest.java
new file mode 100644
index 00000000..d8f048e5
--- /dev/null
+++ b/driver/src/test/java/com/code_intelligence/jazzer/driver/FuzzTargetRunnerTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2022 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.driver;
+
+import com.code_intelligence.jazzer.api.Jazzer;
+import com.code_intelligence.jazzer.runtime.CoverageMap;
+import com.code_intelligence.jazzer.runtime.UnsafeProvider;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import sun.misc.Unsafe;
+
+public class FuzzTargetRunnerTest {
+ private static final Pattern DEDUP_TOKEN_PATTERN =
+ Pattern.compile("(?m)^DEDUP_TOKEN: ([0-9a-f]{16})$");
+ private static final Unsafe UNSAFE = UnsafeProvider.getUnsafe();
+ private static final ByteArrayOutputStream recordedErr = new ByteArrayOutputStream();
+ private static final ByteArrayOutputStream recordedOut = new ByteArrayOutputStream();
+ private static boolean fuzzerInitializeRan = false;
+ private static boolean finishedAllNonCrashingRuns = false;
+
+ public static void fuzzerInitialize() {
+ fuzzerInitializeRan = true;
+ }
+
+ public static void fuzzerTestOneInput(byte[] data) {
+ switch (new String(data, StandardCharsets.UTF_8)) {
+ case "no crash":
+ CoverageMap.recordCoverage(0);
+ return;
+ case "first finding":
+ CoverageMap.recordCoverage(1);
+ throw new IllegalArgumentException("first finding");
+ case "second finding":
+ CoverageMap.recordCoverage(2);
+ Jazzer.reportFindingFromHook(new StackOverflowError("second finding"));
+ throw new IllegalArgumentException("not reported");
+ case "crash":
+ CoverageMap.recordCoverage(3);
+ throw new IllegalArgumentException("crash");
+ }
+ }
+
+ public static void fuzzerTearDown() {
+ String errOutput = new String(recordedErr.toByteArray(), StandardCharsets.UTF_8);
+ assert errOutput.contains("== Java Exception: java.lang.IllegalArgumentException: crash");
+ String outOutput = new String(recordedOut.toByteArray(), StandardCharsets.UTF_8);
+ assert DEDUP_TOKEN_PATTERN.matcher(outOutput).find();
+
+ assert finishedAllNonCrashingRuns : "Did not finish all expected runs before crashing";
+ assert CoverageMap.getCoveredIds().equals(Stream.of(0, 1, 2, 3).collect(Collectors.toSet()));
+ assert UNSAFE.getByte(CoverageMap.countersAddress) == 2;
+ assert UNSAFE.getByte(CoverageMap.countersAddress + 1) == 2;
+ assert UNSAFE.getByte(CoverageMap.countersAddress + 2) == 2;
+ assert UNSAFE.getByte(CoverageMap.countersAddress + 3) == 1;
+ // FuzzTargetRunner calls _Exit after this function, so the test would fail unless this line is
+ // executed. Use halt rather than exit to get around FuzzTargetRunner's shutdown hook calling
+ // fuzzerTearDown, which would otherwise result in a shutdown hook loop.
+ Runtime.getRuntime().halt(0);
+ }
+
+ public static void main(String[] args) {
+ PrintStream recordingErr = new TeeOutputStream(new PrintStream(recordedErr, true), System.err);
+ System.setErr(recordingErr);
+ PrintStream recordingOut = new TeeOutputStream(new PrintStream(recordedOut, true), System.out);
+ System.setOut(recordingOut);
+
+ System.setProperty("jazzer.target_class", FuzzTargetRunnerTest.class.getName());
+ // Keep going past all "no crash", "first finding" and "second finding" runs, then crash.
+ System.setProperty("jazzer.keep_going", "3");
+
+ // Use a loop to simulate two findings with the same stack trace and thus verify that keep_going
+ // works as advertised.
+ for (int i = 1; i < 3; i++) {
+ int result = FuzzTargetRunner.runOne("no crash".getBytes(StandardCharsets.UTF_8));
+
+ assert result == 0;
+ assert !FuzzTargetRunner.useFuzzedDataProvider;
+ assert fuzzerInitializeRan;
+ assert CoverageMap.getCoveredIds().equals(Stream.of(0).collect(Collectors.toSet()));
+ assert UNSAFE.getByte(CoverageMap.countersAddress) == i;
+ assert UNSAFE.getByte(CoverageMap.countersAddress + 1) == 0;
+ assert UNSAFE.getByte(CoverageMap.countersAddress + 2) == 0;
+ assert UNSAFE.getByte(CoverageMap.countersAddress + 3) == 0;
+
+ String errOutput = new String(recordedErr.toByteArray(), StandardCharsets.UTF_8);
+ assert errOutput.isEmpty();
+ String outOutput = new String(recordedOut.toByteArray(), StandardCharsets.UTF_8);
+ assert outOutput.isEmpty();
+ }
+
+ String firstDedupToken = null;
+ for (int i = 1; i < 3; i++) {
+ int result = FuzzTargetRunner.runOne("first finding".getBytes(StandardCharsets.UTF_8));
+
+ assert result == 0;
+ assert CoverageMap.getCoveredIds().equals(Stream.of(0, 1).collect(Collectors.toSet()));
+ assert UNSAFE.getByte(CoverageMap.countersAddress) == 2;
+ assert UNSAFE.getByte(CoverageMap.countersAddress + 1) == i;
+ assert UNSAFE.getByte(CoverageMap.countersAddress + 2) == 0;
+ assert UNSAFE.getByte(CoverageMap.countersAddress + 3) == 0;
+
+ String errOutput = new String(recordedErr.toByteArray(), StandardCharsets.UTF_8);
+ String outOutput = new String(recordedOut.toByteArray(), StandardCharsets.UTF_8);
+ if (i == 1) {
+ assert errOutput.contains(
+ "== Java Exception: java.lang.IllegalArgumentException: first finding");
+ Matcher dedupTokenMatcher = DEDUP_TOKEN_PATTERN.matcher(outOutput);
+ assert dedupTokenMatcher.find();
+ firstDedupToken = dedupTokenMatcher.group();
+ recordedErr.reset();
+ recordedOut.reset();
+ } else {
+ assert errOutput.isEmpty();
+ assert outOutput.isEmpty();
+ }
+ }
+
+ for (int i = 1; i < 3; i++) {
+ int result = FuzzTargetRunner.runOne("second finding".getBytes(StandardCharsets.UTF_8));
+
+ assert result == 0;
+ assert CoverageMap.getCoveredIds().equals(Stream.of(0, 1, 2).collect(Collectors.toSet()));
+ assert UNSAFE.getByte(CoverageMap.countersAddress) == 2;
+ assert UNSAFE.getByte(CoverageMap.countersAddress + 1) == 2;
+ assert UNSAFE.getByte(CoverageMap.countersAddress + 2) == i;
+ assert UNSAFE.getByte(CoverageMap.countersAddress + 3) == 0;
+
+ String errOutput = new String(recordedErr.toByteArray(), StandardCharsets.UTF_8);
+ String outOutput = new String(recordedOut.toByteArray(), StandardCharsets.UTF_8);
+ if (i == 1) {
+ // Verify that the StackOverflowError is wrapped in security issue and contains reproducer
+ // information.
+ assert errOutput.contains(
+ "== Java Exception: com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow: Stack overflow (use ");
+ assert !errOutput.contains("not reported");
+ Matcher dedupTokenMatcher = DEDUP_TOKEN_PATTERN.matcher(outOutput);
+ assert dedupTokenMatcher.find();
+ assert !firstDedupToken.equals(dedupTokenMatcher.group());
+ recordedErr.reset();
+ recordedOut.reset();
+ } else {
+ assert errOutput.isEmpty();
+ assert outOutput.isEmpty();
+ }
+ }
+
+ finishedAllNonCrashingRuns = true;
+
+ FuzzTargetRunner.runOne("crash".getBytes(StandardCharsets.UTF_8));
+
+ throw new IllegalStateException("Expected FuzzTargetRunner to call fuzzerTearDown");
+ }
+
+ /**
+ * An OutputStream that prints to two OutputStreams simultaneously.
+ */
+ private static class TeeOutputStream extends PrintStream {
+ private final PrintStream otherOut;
+ public TeeOutputStream(PrintStream out1, PrintStream out2) {
+ super(out1, true);
+ this.otherOut = out2;
+ }
+
+ @Override
+ public void flush() {
+ super.flush();
+ otherOut.flush();
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ otherOut.close();
+ }
+
+ @Override
+ public void write(int b) {
+ super.write(b);
+ otherOut.write(b);
+ }
+
+ @Override
+ public void write(byte[] buf, int off, int len) {
+ super.write(buf, off, len);
+ otherOut.write(buf, off, len);
+ }
+ }
+}