Skip to content

Commit

Permalink
tests: Verify emitted warnings and errors
Browse files Browse the repository at this point in the history
  • Loading branch information
fmeum committed Oct 16, 2023
1 parent 7db961f commit bc46f99
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 27 deletions.
3 changes: 3 additions & 0 deletions bazel/fuzz_target.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ def java_fuzz_target_test(
allowed_findings = [],
# By default, expect a crash iff allowed_findings isn't empty.
expect_crash = None,
# If empty, expect no warnings or errors, if not empty, expect one matching the given regex.
expected_warning_or_error = "",
**kwargs):
if target_class:
fuzzer_args = fuzzer_args + ["--target_class=" + target_class]
Expand Down Expand Up @@ -115,6 +117,7 @@ def java_fuzz_target_test(
str(verify_crash_reproducer),
str(expect_crash),
str(launcher_variant == "java"),
"'" + expected_warning_or_error + "'",
"'" + ",".join(allowed_findings) + "'",
] + fuzzer_args,
data = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -65,6 +66,7 @@ public static void main(String[] args) {
boolean shouldVerifyCrashReproducer;
boolean expectCrash;
boolean usesJavaLauncher;
Optional<String> expectedWarningOrError;
Set<String> allowedFindings;
List<String> arguments;
try {
Expand All @@ -78,12 +80,17 @@ public static void main(String[] args) {
shouldVerifyCrashReproducer = Boolean.parseBoolean(args[5]);
expectCrash = Boolean.parseBoolean(args[6]);
usesJavaLauncher = Boolean.parseBoolean(args[7]);
if (args[8].isEmpty()) {
expectedWarningOrError = Optional.empty();
} else {
expectedWarningOrError = Optional.of(args[8]);
}
allowedFindings =
Arrays.stream(args[8].split(",")).filter(s -> !s.isEmpty()).collect(Collectors.toSet());
Arrays.stream(args[9].split(",")).filter(s -> !s.isEmpty()).collect(Collectors.toSet());
// Map all files/dirs to real location
arguments =
Arrays.stream(args)
.skip(9)
.skip(10)
.map(arg -> arg.startsWith("-") ? arg : runfiles.rlocation(arg))
.collect(toList());
} catch (IOException | ArrayIndexOutOfBoundsException e) {
Expand Down Expand Up @@ -150,9 +157,14 @@ public static void main(String[] args) {

try {
Process process = processBuilder.start();
boolean sawErrorWithStackTrace;
try {
verifyFuzzerOutput(
process.getErrorStream(), allowedFindings, arguments.contains("--nohooks"));
sawErrorWithStackTrace =
verifyFuzzerOutput(
process.getErrorStream(),
allowedFindings,
arguments.contains("--nohooks"),
expectedWarningOrError);
} finally {
process.getErrorStream().close();
}
Expand All @@ -166,10 +178,11 @@ public static void main(String[] args) {
System.exit(0);
}
// Assert that we either found a crash in Java (exit code 77), a sanitizer crash (exit code
// 76), or a timeout (exit code 70).
// 76), a timeout (exit code 70) or an error with stack trace (exit code 1).
if (exitCode != 76
&& exitCode != 77
&& !(allowedFindings.contains("timeout") && exitCode == 70)) {
&& !(allowedFindings.contains("timeout") && exitCode == 70)
&& !(sawErrorWithStackTrace && exitCode == 1)) {
System.err.printf("Did expect a crash, but Jazzer exited with exit code %d%n", exitCode);
System.exit(1);
}
Expand Down Expand Up @@ -207,25 +220,57 @@ public static void main(String[] args) {
System.exit(0);
}

private static void verifyFuzzerOutput(
InputStream fuzzerOutput, Set<String> expectedFindings, boolean noHooks) throws IOException {
List<String> stackTrace;
// Returns true if the fuzzer failed with an error and there was a stack trace.
private static boolean verifyFuzzerOutput(
InputStream fuzzerOutput,
Set<String> expectedFindings,
boolean noHooks,
Optional<String> expectedWarningOrError)
throws IOException {
List<String> lines;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(fuzzerOutput))) {
stackTrace =
reader
.lines()
.peek(System.err::println)
.filter(
line ->
line.startsWith(EXCEPTION_PREFIX)
|| line.startsWith(FRAME_PREFIX)
|| line.equals(THREAD_DUMP_HEADER)
|| SANITIZER_FINDING.matcher(line).find())
.collect(toList());
lines = reader.lines().collect(toList());
}

List<String> warningsAndErrors =
lines.stream()
.filter(line -> line.startsWith("WARN") || line.startsWith("ERROR"))
.collect(toList());
boolean sawError = warningsAndErrors.stream().anyMatch(line -> line.startsWith("ERROR"));
if (!expectedWarningOrError.isPresent() && !warningsAndErrors.isEmpty()) {
throw new IllegalStateException(
"Did not expect warnings or errors, but got:\n" + String.join("\n", warningsAndErrors));
}
if (expectedWarningOrError.isPresent()) {
if (warningsAndErrors.isEmpty()) {
throw new IllegalStateException("Expected a warning or error, but did not get any");
}
String unexpectedWarningsAndErrors =
warningsAndErrors.stream()
.filter(line -> !expectedWarningOrError.get().equals(line))
.collect(Collectors.joining("\n"));
if (!unexpectedWarningsAndErrors.isEmpty()) {
throw new IllegalStateException(
"Got unexpected warnings or errors: " + unexpectedWarningsAndErrors);
}
}

List<String> stackTrace =
lines.stream()
.peek(System.err::println)
.filter(
line ->
line.startsWith(EXCEPTION_PREFIX)
|| line.startsWith(FRAME_PREFIX)
|| line.equals(THREAD_DUMP_HEADER)
|| SANITIZER_FINDING.matcher(line).find())
.collect(toList());
if (expectedFindings.isEmpty()) {
if (stackTrace.isEmpty()) {
return;
return false;
}
if (!warningsAndErrors.isEmpty()) {
return sawError;
}
throw new IllegalStateException(
String.format(
Expand All @@ -244,7 +289,7 @@ private static void verifyFuzzerOutput(
if (expectedFindings.size() != 1) {
throw new IllegalStateException("Cannot expect both a native and other findings");
}
return;
return false;
}
if (expectedFindings.contains("timeout")) {
if (!stackTrace.contains(THREAD_DUMP_HEADER) || stackTrace.size() < 3) {
Expand All @@ -254,7 +299,7 @@ private static void verifyFuzzerOutput(
if (expectedFindings.size() != 1) {
throw new IllegalStateException("Cannot expect both a timeout and other findings");
}
return;
return false;
}
List<String> findings =
stackTrace.stream()
Expand Down Expand Up @@ -291,6 +336,7 @@ private static void verifyFuzzerOutput(
"Unexpected strack trace frames:%n%n%s%n%nin:%n%s",
String.join("\n", unexpectedFrames), String.join("\n", stackTrace)));
}
return false;
}

private static void verifyCrashReproducer(
Expand Down
3 changes: 3 additions & 0 deletions examples/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ java_fuzz_target_test(
allowed_findings = ["native"],
env = {"EXAMPLE_NATIVE_LIB": "native_asan"},
env_inherit = ["CC"],
expected_warning_or_error = "WARN: Jazzer is not compatible with LeakSanitizer. Leaks are not reported.",
fuzzer_args = [
"--asan",
],
Expand Down Expand Up @@ -351,6 +352,7 @@ java_fuzz_target_test(
"src/main/java/com/example/FastJsonFuzzer.java",
],
allowed_findings = ["java.lang.NumberFormatException"],
expected_warning_or_error = "WARN: Some hooks could not be applied to class files built for Java 7 or lower.",
target_class = "com.example.FastJsonFuzzer",
deps = [
"@maven//:com_alibaba_fastjson",
Expand All @@ -376,6 +378,7 @@ java_fuzz_target_test(
"java.lang.NumberFormatException",
"java.lang.NullPointerException",
],
expected_warning_or_error = "WARN: Some hooks could not be applied to class files built for Java 7 or lower.",
fuzzer_args = [
"--keep_going=7",
],
Expand Down
10 changes: 7 additions & 3 deletions examples/junit/src/test/java/com/example/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,13 @@ java_fuzz_target_test(
java_fuzz_target_test(
name = "PerExecutionLifecycleFuzzTest",
srcs = ["PerExecutionLifecycleFuzzTest.java"],
allowed_findings = ["com.example.TestSuccessfulException"],
expect_crash = True,
expected_warning_or_error = "ERROR: com.example.TestSuccessfulException: Lifecycle methods invoked as expected",
fuzzer_args = [
"-runs=3",
],
target_class = "com.example.PerExecutionLifecycleFuzzTest",
verify_crash_input = False,
verify_crash_reproducer = False,
runtime_deps = [
":junit_runtime",
Expand All @@ -94,8 +96,8 @@ java_fuzz_target_test(
java_fuzz_target_test(
name = "PerExecutionLifecycleWithFindingFuzzTest",
srcs = ["PerExecutionLifecycleWithFindingFuzzTest.java"],
# TODO: The TestSuccessfulException thrown in @AfterAll is swallowed when run from the CLI.
allowed_findings = ["java.io.IOException"],
expected_warning_or_error = "ERROR: com.example.TestSuccessfulException: Lifecycle methods invoked as expected",
fuzzer_args = [
"-runs=3",
],
Expand All @@ -117,11 +119,13 @@ java_fuzz_target_test(
java_fuzz_target_test(
name = "PerTestLifecycleFuzzTest",
srcs = ["PerTestLifecycleFuzzTest.java"],
allowed_findings = ["com.example.TestSuccessfulException"],
expect_crash = True,
expected_warning_or_error = "ERROR: com.example.TestSuccessfulException: Lifecycle methods invoked as expected",
fuzzer_args = [
"-runs=3",
],
target_class = "com.example.PerTestLifecycleFuzzTest",
verify_crash_input = False,
verify_crash_reproducer = False,
runtime_deps = [
":junit_runtime",
Expand Down
1 change: 1 addition & 0 deletions sanitizers/src/test/java/com/example/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ java_fuzz_target_test(
"ExpressionLanguageInjection.java",
],
allowed_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh"],
expected_warning_or_error = "WARN: Some hooks could not be applied to class files built for Java 7 or lower.",
target_class = "com.example.ExpressionLanguageInjection",
# The reproducer can't find jaz.Zer and thus doesn't crash.
verify_crash_reproducer = False,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ private static AtomicReference<BiPredicate<String, Integer>> getConnectionPermit
return (AtomicReference<BiPredicate<String, Integer>>)
ssrfSanitizer.getField("connectionPermitted").get(null);
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
System.err.println("WARNING: ");
System.err.println("WARN: ");
e.printStackTrace();
return null;
}
Expand Down
6 changes: 6 additions & 0 deletions tests/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ java_fuzz_target_test(
name = "NoCoverageFuzzer",
timeout = "short",
srcs = ["src/test/java/com/example/NoCoverageFuzzer.java"],
expected_warning_or_error = "WARNING: no interesting inputs were found so far. Is the code instrumented for coverage?",
fuzzer_args = [
"-runs=10",
"--instrumentation_excludes=**",
Expand Down Expand Up @@ -485,6 +486,11 @@ java_fuzz_target_test(
timeout = "short",
srcs = ["src/test/java/com/example/OfflineInstrumentedFuzzer.java"],
allowed_findings = ["java.lang.IllegalStateException"],
fuzzer_args = [
# Do not instrument the JaCoCo agent.
"--instrumentation_includes=com.example.*",
"--custom_hook_includes=com.example.*",
],
target_class = "com.example.OfflineInstrumentedFuzzer",
deps = [
":OfflineInstrumentedTargetInstrumented",
Expand Down

0 comments on commit bc46f99

Please sign in to comment.