aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFabian Meumertzheim <meumertzheim@code-intelligence.com>2021-03-22 14:48:58 +0100
committerGitHub <noreply@github.com>2021-03-22 14:48:58 +0100
commit71ac55c6fc9d808bcc8a8e8d895f7f20141bec86 (patch)
treedfa557a023d1413799c24dbd1373d8c42c2ee8bb
parent20d72b43a58f5ffcb807245a854d7eb178c4b8b6 (diff)
downloadjazzer-api-71ac55c6fc9d808bcc8a8e8d895f7f20141bec86.tar.gz
Do not intercept JVM-internal C stdlib calls (#45)
* Replace uses of quick_exit and at_quick_exit quick_exit is not supported on macOS, but can easily replaced by a call to _Exit after running our cleanup manually. * Run buildifier --lint=fix -r . * Build libFuzzer from source Building libFuzzer from source is easy and has multiple advantages: * The clang distributed with XCode on macOS does not include libFuzzer. * Applying a small patch to libFuzzer will allow us to replace the --wrap linker feature, which is not supported on platforms other than Linux. * Replace -Wl,--wrap with a source code patch * Pin non-native rules_python * Print exit code on test failure * Do not intercept JVM-internal C stdlib calls The JVM frequently calls strcmp/memcmp/..., which fills up the table of recent compares with entries that are either duplicates of values already reported by the bytecode instrumentation or JDK-internal strings that are not relevant for fuzzing. This commit adds an ignorelist to the C stdlib interceptors that filters out calls from known JVM libraries. If the fuzz target has not yet loaded a native library, all such callbacks are ignored, which greatly improves fuzzer performance for string-heavy targets. E.g., JsonSanitizerDenylistFuzzer takes < 1 million runs now when it used to take over 3 million.
-rw-r--r--BUILD.bazel1
-rw-r--r--WORKSPACE.bazel27
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt1
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt2
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel1
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt1
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java35
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java2
-rw-r--r--bazel/fuzz_target.bzl8
-rwxr-xr-xbazel/fuzz_target_test_wrapper.sh1
-rw-r--r--driver/BUILD.bazel17
-rw-r--r--driver/fuzz_target_runner.cpp2
-rw-r--r--driver/libfuzzer_callbacks.cpp115
-rw-r--r--driver/libfuzzer_driver.cpp18
-rw-r--r--driver/libfuzzer_fuzz_target.cpp31
-rw-r--r--driver/sanitizer_symbols_for_tests.cpp2
-rw-r--r--examples/BUILD.bazel1
-rw-r--r--third_party/BUILD.bazel3
-rw-r--r--third_party/jni/BUILD.bazel2
-rw-r--r--third_party/libFuzzer-make-interceptors-configurable.patch109
-rw-r--r--third_party/libFuzzer-pass-death-callback-to-jazzer.patch28
-rw-r--r--third_party/libFuzzer.BUILD21
22 files changed, 376 insertions, 52 deletions
diff --git a/BUILD.bazel b/BUILD.bazel
index 8c30be59..3f81fdd2 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -2,7 +2,6 @@ load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar")
load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "define_kt_toolchain")
load("@io_bazel_rules_kotlin//kotlin/internal:opts.bzl", "kt_javac_options", "kt_kotlinc_options")
load("@rules_pkg//:pkg.bzl", "pkg_tar")
-load("@rules_jvm_external//:defs.bzl", "java_export")
kt_kotlinc_options(
name = "kotlinc_options",
diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel
index c6a0f0cb..d54cdba4 100644
--- a/WORKSPACE.bazel
+++ b/WORKSPACE.bazel
@@ -14,8 +14,14 @@ http_archive(
],
)
-# bazelbuild/bazel-skylib
+# bazelbuild/rules_python
+http_archive(
+ name = "rules_python",
+ sha256 = "b6d46438523a3ec0f3cead544190ee13223a52f6a6765a29eae7b7cc24cc83a0",
+ url = "https://github.com/bazelbuild/rules_python/releases/download/0.1.0/rules_python-0.1.0.tar.gz",
+)
+# bazelbuild/bazel-skylib
http_archive(
name = "bazel_skylib",
sha256 = "ebdf850bfef28d923a2cc67ddca86355a449b5e4f38b0a70e584dc24e5984aa6",
@@ -78,7 +84,6 @@ http_archive(
urls = ["https://github.com/bazelbuild/rules_kotlin/releases/download/%s/rules_kotlin_release.tgz" % rules_kotlin_version],
)
-load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories", "kt_register_toolchains")
load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories")
kotlin_repositories()
@@ -144,8 +149,6 @@ rules_pkg_version = "0.3.0"
rules_pkg_sha = "6b5969a7acd7b60c02f816773b06fcf32fbe8ba0c7919ccdc2df4f8fb923804a"
-load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
-
http_archive(
name = "rules_pkg",
sha256 = rules_pkg_sha,
@@ -160,7 +163,6 @@ load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies")
rules_pkg_dependencies()
# bazelbuild/rules_foreign_cc
-
rules_foreign_cc_commit = "da99da47a0befc3dfbf65739190cd374f836f21d"
http_archive(
@@ -175,7 +177,6 @@ load("@rules_foreign_cc//:workspace_definitions.bzl", "rules_foreign_cc_dependen
rules_foreign_cc_dependencies()
# libjpeg_turbo
-
http_archive(
name = "libjpeg_turbo",
build_file = "//third_party:libjpeg_turbo.BUILD",
@@ -185,7 +186,6 @@ http_archive(
)
# JaCoCo
-
jacoco_commit = "178d49870056b8a1f8ea6915e804d28b0dda5609"
jacoco_sha = "da48fb5ae4ec3ffc659d4de18232aedea99476935f4ce4b0605f2d6aa1dc2553"
@@ -200,3 +200,16 @@ http_archive(
strip_prefix = "jacoco-%s" % jacoco_commit,
url = "https://github.com/jacoco/jacoco/archive/178d49870056b8a1f8ea6915e804d28b0dda5609.tar.gz",
)
+
+# libFuzzer
+http_archive(
+ name = "libFuzzer",
+ build_file = "//third_party:libFuzzer.BUILD",
+ patches = [
+ "//third_party:libFuzzer-make-interceptors-configurable.patch",
+ "//third_party:libFuzzer-pass-death-callback-to-jazzer.patch",
+ ],
+ sha256 = "a78949f86fc9852f51b11ceb3e6c2c61bb6e4ebb073198cebddc82451f708adf",
+ strip_prefix = "llvm-project-llvmorg-12.0.0-rc3",
+ url = "https://github.com/llvm/llvm-project/archive/llvmorg-12.0.0-rc3.tar.gz",
+)
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt
index 43e8a488..47ebab3c 100644
--- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt
+++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt
@@ -69,6 +69,7 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) {
"div" -> setOf(InstrumentationType.DIV)
"gep" -> setOf(InstrumentationType.GEP)
"indir" -> setOf(InstrumentationType.INDIR)
+ "native" -> setOf(InstrumentationType.NATIVE)
"all" -> InstrumentationType.values().toSet()
else -> {
println("WARN: Skipping unknown instrumentation type $it")
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt
index 0e304f45..35ee3959 100644
--- a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt
+++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt
@@ -18,6 +18,7 @@ import com.code_intelligence.jazzer.instrumentor.ClassInstrumentor
import com.code_intelligence.jazzer.instrumentor.Hook
import com.code_intelligence.jazzer.instrumentor.InstrumentationType
import com.code_intelligence.jazzer.instrumentor.loadHooks
+import com.code_intelligence.jazzer.runtime.NativeLibHooks
import com.code_intelligence.jazzer.runtime.TraceCmpHooks
import com.code_intelligence.jazzer.runtime.TraceDivHooks
import com.code_intelligence.jazzer.runtime.TraceIndirHooks
@@ -81,6 +82,7 @@ internal class RuntimeInstrumentor(
InstrumentationType.CMP -> TraceCmpHooks::class.java
InstrumentationType.DIV -> TraceDivHooks::class.java
InstrumentationType.INDIR -> TraceIndirHooks::class.java
+ InstrumentationType.NATIVE -> NativeLibHooks::class.java
else -> null
}
}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel
index d68ec102..ee16b40c 100644
--- a/agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel
+++ b/agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_binary", "java_library")
load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library")
java_binary(
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt
index 50904e61..78793842 100644
--- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt
+++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt
@@ -23,6 +23,7 @@ enum class InstrumentationType {
DIV,
GEP,
INDIR,
+ NATIVE,
}
internal interface Instrumentor {
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java
new file mode 100644
index 00000000..495cad7c
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java
@@ -0,0 +1,35 @@
+// Copyright 2021 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.runtime;
+
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+
+@SuppressWarnings("unused")
+final public class NativeLibHooks {
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Runtime",
+ targetMethod = "loadLibrary", targetMethodDescriptor = "(Ljava/lang/String;)V")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.System",
+ targetMethod = "loadLibrary", targetMethodDescriptor = "(Ljava/lang/String;)V")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Runtime", targetMethod = "load",
+ targetMethodDescriptor = "(Ljava/lang/String;)V")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.System", targetMethod = "load",
+ targetMethodDescriptor = "(Ljava/lang/String;)V")
+ public static void
+ loadLibraryHook(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ TraceDataFlowNativeCallbacks.handleLibraryLoad();
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java
index 147386ae..f779cec6 100644
--- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java
@@ -73,4 +73,6 @@ final public class TraceDataFlowNativeCallbacks {
// as the stack layout required for the call can't be achieved without local variables.
return Long.compare(arg1, arg2);
}
+
+ public static native void handleLibraryLoad();
}
diff --git a/bazel/fuzz_target.bzl b/bazel/fuzz_target.bzl
index 04cf32e3..9f2fe2d5 100644
--- a/bazel/fuzz_target.bzl
+++ b/bazel/fuzz_target.bzl
@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+load("@rules_java//java:defs.bzl", "java_binary")
+
def java_fuzz_target_test(
name,
target_class,
@@ -29,14 +31,12 @@ def java_fuzz_target_test(
"Jazzer-Fuzz-Target-Class: %s" % target_class,
]
if hook_classes:
- deploy_manifest_lines += [
- "Jazzer-Hook-Classes: %s" % ":".join(hook_classes),
- ]
+ deploy_manifest_lines.append("Jazzer-Hook-Classes: %s" % ":".join(hook_classes))
# Deps can only be specified on java_binary targets with sources, which
# excludes e.g. Kotlin libraries wrapped into java_binary via runtime_deps.
target_deps = deps + ["//agent/src/main/java/com/code_intelligence/jazzer/api"] if srcs else []
- native.java_binary(
+ java_binary(
name = target_name,
srcs = srcs,
visibility = ["//visibility:private"],
diff --git a/bazel/fuzz_target_test_wrapper.sh b/bazel/fuzz_target_test_wrapper.sh
index 7a2a9f7f..061ed3ae 100755
--- a/bazel/fuzz_target_test_wrapper.sh
+++ b/bazel/fuzz_target_test_wrapper.sh
@@ -24,5 +24,6 @@ if [ $exit_code -eq 77 ] || [ $exit_code -eq 76 ]
then
exit 0
else
+ echo "Unexpected exit code: $exit_code"
exit 1
fi
diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel
index 2c17b947..599fcac0 100644
--- a/driver/BUILD.bazel
+++ b/driver/BUILD.bazel
@@ -1,9 +1,4 @@
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
-load(
- "@bazel_tools//tools/jdk:default_java_toolchain.bzl",
- "java_runtime_files",
-)
-load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
cc_library(
name = "sanitizer_hooks_with_pc",
@@ -47,6 +42,9 @@ cc_library(
"signal_handler.h",
"utils.h",
],
+ linkopts = [
+ "-ldl",
+ ],
visibility = ["//visibility:public"],
deps = [
":sanitizer_hooks_with_pc",
@@ -64,13 +62,10 @@ cc_binary(
data = [
"//agent:jazzer_agent_deploy.jar",
],
- linkopts = [
- "-Wl,--wrap=__sanitizer_set_death_callback",
- "-fsanitize=fuzzer",
- ],
visibility = ["//visibility:public"],
deps = [
":jvm_tooling_lib",
+ "@libFuzzer",
],
)
@@ -81,12 +76,12 @@ cc_binary(
"//agent:jazzer_agent_deploy.jar",
],
linkopts = [
- "-Wl,--wrap=__sanitizer_set_death_callback",
- "-fsanitize=fuzzer,address",
+ "-fsanitize=address",
],
visibility = ["//visibility:public"],
deps = [
":jvm_tooling_lib",
+ "@libFuzzer",
],
)
diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp
index ae3602e0..0d83264d 100644
--- a/driver/fuzz_target_runner.cpp
+++ b/driver/fuzz_target_runner.cpp
@@ -166,7 +166,7 @@ FuzzTargetRunner::FuzzTargetRunner(
LOG(ERROR) << "Invalid dedup token (expected up to 16 hex digits): '"
<< str_token << "'";
// Don't let libFuzzer print a crash stack trace.
- std::quick_exit(1);
+ _Exit(1);
}
}
}
diff --git a/driver/libfuzzer_callbacks.cpp b/driver/libfuzzer_callbacks.cpp
index d1c754af..398d69d8 100644
--- a/driver/libfuzzer_callbacks.cpp
+++ b/driver/libfuzzer_callbacks.cpp
@@ -14,10 +14,14 @@
#include "libfuzzer_callbacks.h"
-#include <algorithm>
+#include <fstream>
#include <iostream>
+#include <utility>
+#include <vector>
+#include "absl/strings/match.h"
#include "absl/strings/str_format.h"
+#include "absl/strings/str_split.h"
#include "glog/logging.h"
#include "sanitizer_hooks_with_pc.h"
#include "third_party/jni/jni.h"
@@ -172,6 +176,106 @@ void JNICALL libfuzzerPcIndirCallback(JNIEnv &env, jclass cls, jint caller_id,
static_cast<uintptr_t>(callee_id));
}
+bool is_using_native_libraries = false;
+std::vector<std::pair<uintptr_t, uintptr_t>> ignore_for_interception_ranges;
+
+extern "C" [[maybe_unused]] bool __sanitizer_weak_is_relevant_pc(
+ void *caller_pc) {
+ // If the fuzz target is not using native libraries, calls to strcmp, memcmp,
+ // etc. should never be intercepted. The values reported if they were at best
+ // duplicate the values received from our bytecode instrumentation and at
+ // worst pollute the table of recent compares with string internal to the JDK.
+ if (!is_using_native_libraries) return false;
+ // If the fuzz target is using native libraries, intercept calls only if they
+ // don't originate from those address ranges that are known to belong to the
+ // JDK.
+ bool should_intercept = std::none_of(
+ ignore_for_interception_ranges.cbegin(),
+ ignore_for_interception_ranges.cend(), [caller_pc](const auto &range) {
+ uintptr_t start;
+ uintptr_t end;
+ std::tie(start, end) = range;
+ auto address = reinterpret_cast<uintptr_t>(caller_pc);
+ return start <= address && address <= end;
+ });
+ if (should_intercept) {
+ std::cout << " PC: " << caller_pc << std::endl;
+ }
+ return should_intercept;
+}
+
+/**
+ * Adds the address ranges of executable segmentes of the library lib_name to
+ * the ignorelist for C standard library function interception (strcmp, memcmp,
+ * ...).
+ */
+void ignoreLibraryForInterception(const std::string &lib_name) {
+ const auto num_address_ranges = ignore_for_interception_ranges.size();
+ std::ifstream loaded_libs("/proc/self/maps");
+ std::string line;
+ while (std::getline(loaded_libs, line)) {
+ if (!absl::StrContains(line, lib_name)) continue;
+ // clang-format off
+ // A typical line looks as follows:
+ // 7f15356c9000-7f1536367000 r-xp 0020d000 fd:01 19275673 /usr/lib/jvm/java-15-openjdk-amd64/lib/server/libjvm.so
+ // clang-format on
+ std::vector<std::string_view> parts =
+ absl::StrSplit(line, ' ', absl::SkipEmpty());
+ if (parts.size() != 6) {
+ std::cout << "ERROR: Invalid format for /proc/self/maps\n"
+ << line << std::endl;
+ exit(1);
+ }
+ // Skip non-executable address ranges.
+ if (!absl::StrContains(parts[1], 'x')) continue;
+ std::string_view range_str = parts[0];
+ std::vector<std::string> range = absl::StrSplit(range_str, '-');
+ if (range.size() != 2) {
+ std::cout
+ << "ERROR: Unexpected address range format in /proc/self/maps line: "
+ << range_str << std::endl;
+ exit(1);
+ }
+ std::size_t pos;
+ auto start = std::stoull(range[0], &pos, 16);
+ if (pos != range[0].size()) {
+ std::cout
+ << "ERROR: Unexpected address range format in /proc/self/maps line: "
+ << range_str << std::endl;
+ exit(1);
+ }
+ auto end = std::stoull(range[1], &pos, 16);
+ if (pos != range[0].size()) {
+ std::cout
+ << "ERROR: Unexpected address range format in /proc/self/maps line: "
+ << range_str << std::endl;
+ exit(1);
+ }
+ ignore_for_interception_ranges.emplace_back(start, end);
+ }
+ const auto num_code_segments =
+ ignore_for_interception_ranges.size() - num_address_ranges;
+ LOG(INFO) << "added " << num_code_segments
+ << " code segment of native library " << lib_name
+ << " to interceptor ignorelist";
+}
+
+const std::vector<std::string> kLibrariesToIgnoreForInterception = {
+ // The driver executable itself can be treated just like a library.
+ "jazzer_driver", "libinstrument.so", "libjava.so",
+ "libjimage.so", "libjli.so", "libjvm.so",
+ "libnet.so", "libverify.so", "libzip.so",
+};
+
+void JNICALL handleLibraryLoad(JNIEnv &env, jclass cls) {
+ if (is_using_native_libraries) return;
+ LOG(INFO) << "detected a native library load, enabling interception for libc "
+ "functions";
+ is_using_native_libraries = true;
+ for (const auto &lib_name : kLibrariesToIgnoreForInterception)
+ ignoreLibraryForInterception(lib_name);
+}
+
void registerCallback(JNIEnv &env, const char *java_hooks_class_name,
const JNINativeMethod *methods, int num_methods) {
auto java_hooks_class = env.FindClass(java_hooks_class_name);
@@ -263,6 +367,15 @@ bool registerFuzzerCallbacks(JNIEnv &env) {
sizeof(indir_methods) / sizeof(indir_methods[0]));
}
+ {
+ JNINativeMethod native_methods[]{{(char *)"handleLibraryLoad",
+ (char *)"()V",
+ (void *)(&handleLibraryLoad)}};
+
+ registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, native_methods,
+ sizeof(native_methods) / sizeof(native_methods[0]));
+ }
+
return env.ExceptionCheck();
}
diff --git a/driver/libfuzzer_driver.cpp b/driver/libfuzzer_driver.cpp
index 2accc40d..4d51104e 100644
--- a/driver/libfuzzer_driver.cpp
+++ b/driver/libfuzzer_driver.cpp
@@ -14,6 +14,8 @@
#include "libfuzzer_driver.h"
+#include <dlfcn.h>
+
#include <algorithm>
#include <filesystem>
#include <fstream>
@@ -48,13 +50,16 @@ DECLARE_bool(fake_pcs);
// Defined in jvm_tooling.cpp
DECLARE_string(id_sync_file);
-extern "C" void __real___sanitizer_set_death_callback(void (*callback)());
-
-// We use the linker opt -Wl,--wrap=__sanitizer_set_death_callback to wrap the
-// symbol defined by sanitizers_common to receive libFuzzer's death callback.
-extern "C" void __wrap___sanitizer_set_death_callback(void (*callback)()) {
+// We apply a patch to libFuzzer to make it call this function instead of
+// __sanitizer_set_death_callback to pass us the death callback.
+extern "C" [[maybe_unused]] void __jazzer_set_death_callback(
+ void (*callback)()) {
jazzer::AbstractLibfuzzerDriver::libfuzzer_print_crashing_input_ = callback;
- __real___sanitizer_set_death_callback(callback);
+ void *sanitizer_set_death_callback =
+ dlsym(RTLD_NEXT, "__sanitizer_set_death_callback");
+ if (sanitizer_set_death_callback != nullptr)
+ reinterpret_cast<void (*)(void (*)())>(sanitizer_set_death_callback)(
+ callback);
}
namespace {
@@ -148,7 +153,6 @@ AbstractLibfuzzerDriver::AbstractLibfuzzerDriver(
}
};
std::atexit(cleanup_fn);
- std::at_quick_exit(cleanup_fn);
}
initJvm(*argv_start);
diff --git a/driver/libfuzzer_fuzz_target.cpp b/driver/libfuzzer_fuzz_target.cpp
index 7f921321..8bbed9a1 100644
--- a/driver/libfuzzer_fuzz_target.cpp
+++ b/driver/libfuzzer_fuzz_target.cpp
@@ -50,10 +50,7 @@ extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
<< std::endl;
}
gLibfuzzerDriver = std::make_unique<Driver>(argc, argv);
- // Run even if we use std::quick_exit to prevent libFuzzer stack trace
- // printing.
std::atexit(&driver_cleanup);
- std::at_quick_exit(&driver_cleanup);
return 0;
}
@@ -62,23 +59,19 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, const size_t size) {
auto result = gLibfuzzerDriver->TestOneInput(data, size);
if (result != jazzer::RunResult::kOk) {
// Fuzzer triggered an exception or assertion in Java code. Skip the
- // uninformative libFuzzer stack trace if possible.
- if (Driver::libfuzzer_print_crashing_input_ != nullptr) {
- std::cerr << "== libFuzzer crashing input ==\n";
- Driver::libfuzzer_print_crashing_input_();
- // DumpReproducer needs to be called after libFuzzer printed its final
- // stats as otherwise it would report incorrect coverage.
- gLibfuzzerDriver->DumpReproducer(data, size);
- if (result == jazzer::RunResult::kDumpAndContinue) {
- // Continue fuzzing after printing the crashing input.
- return 0;
- }
- // Exit directly without invoking libFuzzer's atexit hook.
- std::quick_exit(Driver::kErrorExitCode);
- } else {
- // libFuzzer failed to register its death callback, exit normally.
- std::exit(1);
+ // uninformative libFuzzer stack trace.
+ std::cerr << "== libFuzzer crashing input ==\n";
+ Driver::libfuzzer_print_crashing_input_();
+ // DumpReproducer needs to be called after libFuzzer printed its final
+ // stats as otherwise it would report incorrect coverage.
+ gLibfuzzerDriver->DumpReproducer(data, size);
+ if (result == jazzer::RunResult::kDumpAndContinue) {
+ // Continue fuzzing after printing the crashing input.
+ return 0;
}
+ // Exit directly without invoking libFuzzer's atexit hook.
+ driver_cleanup();
+ _Exit(Driver::kErrorExitCode);
}
return 0;
}
diff --git a/driver/sanitizer_symbols_for_tests.cpp b/driver/sanitizer_symbols_for_tests.cpp
index fb2f6d08..b671ff66 100644
--- a/driver/sanitizer_symbols_for_tests.cpp
+++ b/driver/sanitizer_symbols_for_tests.cpp
@@ -34,5 +34,5 @@ void __sanitizer_cov_trace_div4(uint32_t val) {}
void __sanitizer_cov_trace_div8(uint64_t val) {}
void __sanitizer_cov_trace_gep(uintptr_t idx) {}
void __sanitizer_cov_trace_pc_indir(uintptr_t callee) {}
-void __real___sanitizer_set_death_callback(void (*callback)()) {}
+void __sanitizer_set_death_callback(void (*callback)()) {}
}
diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel
index 9b5951a0..4bc7deb8 100644
--- a/examples/BUILD.bazel
+++ b/examples/BUILD.bazel
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_binary")
load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library")
load("//bazel:fuzz_target.bzl", "java_fuzz_target_test")
diff --git a/third_party/BUILD.bazel b/third_party/BUILD.bazel
index fd65eb9b..b391ce54 100644
--- a/third_party/BUILD.bazel
+++ b/third_party/BUILD.bazel
@@ -2,5 +2,8 @@ exports_files([
"gflags-use-double-dash-args.patch",
"jacoco-make-probe-inserter-subclassable.patch",
"jacoco_internal.BUILD",
+ "libFuzzer-make-interceptors-configurable.patch",
+ "libFuzzer-pass-death-callback-to-jazzer.patch",
+ "libFuzzer.BUILD",
"libjpeg_turbo.BUILD",
])
diff --git a/third_party/jni/BUILD.bazel b/third_party/jni/BUILD.bazel
index 4d5fdd00..cda76ef0 100644
--- a/third_party/jni/BUILD.bazel
+++ b/third_party/jni/BUILD.bazel
@@ -1,3 +1,5 @@
+load("@rules_cc//cc:defs.bzl", "cc_import", "cc_library")
+
cc_library(
name = "jni",
visibility = ["//visibility:public"],
diff --git a/third_party/libFuzzer-make-interceptors-configurable.patch b/third_party/libFuzzer-make-interceptors-configurable.patch
new file mode 100644
index 00000000..9420c4aa
--- /dev/null
+++ b/third_party/libFuzzer-make-interceptors-configurable.patch
@@ -0,0 +1,109 @@
+diff --git compiler-rt/lib/fuzzer/FuzzerInterceptors.cpp compiler-rt/lib/fuzzer/FuzzerInterceptors.cpp
+index b87798603fda..10e34ee86cce 100644
+--- compiler-rt/lib/fuzzer/FuzzerInterceptors.cpp
++++ compiler-rt/lib/fuzzer/FuzzerInterceptors.cpp
+@@ -147,11 +147,18 @@ DEFINE_REAL(char *, strstr, const char *, const char *)
+ DEFINE_REAL(char *, strcasestr, const char *, const char *)
+ DEFINE_REAL(void *, memmem, const void *, size_t, const void *, size_t)
+
++extern "C" __attribute__((weak)) bool
++__sanitizer_weak_is_relevant_pc(void * caller_pc) {
++ return false;
++}
++
+ ATTRIBUTE_INTERFACE int bcmp(const char *s1, const char *s2, size_t n) {
+ if (!FuzzerInited)
+ return internal_memcmp(s1, s2, n);
+ int result = REAL(bcmp)(s1, s2, n);
+- __sanitizer_weak_hook_memcmp(GET_CALLER_PC(), s1, s2, n, result);
++ void *caller_pc = GET_CALLER_PC();
++ if (__sanitizer_weak_is_relevant_pc(caller_pc))
++ __sanitizer_weak_hook_memcmp(caller_pc, s1, s2, n, result);
+ return result;
+ }
+
+@@ -159,7 +166,9 @@ ATTRIBUTE_INTERFACE int memcmp(const void *s1, const void *s2, size_t n) {
+ if (!FuzzerInited)
+ return internal_memcmp(s1, s2, n);
+ int result = REAL(memcmp)(s1, s2, n);
+- __sanitizer_weak_hook_memcmp(GET_CALLER_PC(), s1, s2, n, result);
++ void *caller_pc = GET_CALLER_PC();
++ if (__sanitizer_weak_is_relevant_pc(caller_pc))
++ __sanitizer_weak_hook_memcmp(caller_pc, s1, s2, n, result);
+ return result;
+ }
+
+@@ -167,7 +176,9 @@ ATTRIBUTE_INTERFACE int strncmp(const char *s1, const char *s2, size_t n) {
+ if (!FuzzerInited)
+ return internal_strncmp(s1, s2, n);
+ int result = REAL(strncmp)(s1, s2, n);
+- __sanitizer_weak_hook_strncmp(GET_CALLER_PC(), s1, s2, n, result);
++ void *caller_pc = GET_CALLER_PC();
++ if (__sanitizer_weak_is_relevant_pc(caller_pc))
++ __sanitizer_weak_hook_strncmp(caller_pc, s1, s2, n, result);
+ return result;
+ }
+
+@@ -175,21 +186,27 @@ ATTRIBUTE_INTERFACE int strcmp(const char *s1, const char *s2) {
+ if (!FuzzerInited)
+ return internal_strcmp(s1, s2);
+ int result = REAL(strcmp)(s1, s2);
+- __sanitizer_weak_hook_strcmp(GET_CALLER_PC(), s1, s2, result);
++ void *caller_pc = GET_CALLER_PC();
++ if (__sanitizer_weak_is_relevant_pc(caller_pc))
++ __sanitizer_weak_hook_strcmp(caller_pc, s1, s2, result);
+ return result;
+ }
+
+ ATTRIBUTE_INTERFACE int strncasecmp(const char *s1, const char *s2, size_t n) {
+ ensureFuzzerInited();
+ int result = REAL(strncasecmp)(s1, s2, n);
+- __sanitizer_weak_hook_strncasecmp(GET_CALLER_PC(), s1, s2, n, result);
++ void *caller_pc = GET_CALLER_PC();
++ if (__sanitizer_weak_is_relevant_pc(caller_pc))
++ __sanitizer_weak_hook_strncasecmp(caller_pc, s1, s2, n, result);
+ return result;
+ }
+
+ ATTRIBUTE_INTERFACE int strcasecmp(const char *s1, const char *s2) {
+ ensureFuzzerInited();
+ int result = REAL(strcasecmp)(s1, s2);
+- __sanitizer_weak_hook_strcasecmp(GET_CALLER_PC(), s1, s2, result);
++ void *caller_pc = GET_CALLER_PC();
++ if (__sanitizer_weak_is_relevant_pc(caller_pc))
++ __sanitizer_weak_hook_strcasecmp(caller_pc, s1, s2, result);
+ return result;
+ }
+
+@@ -197,14 +214,18 @@ ATTRIBUTE_INTERFACE char *strstr(const char *s1, const char *s2) {
+ if (!FuzzerInited)
+ return internal_strstr(s1, s2);
+ char *result = REAL(strstr)(s1, s2);
+- __sanitizer_weak_hook_strstr(GET_CALLER_PC(), s1, s2, result);
++ void *caller_pc = GET_CALLER_PC();
++ if (__sanitizer_weak_is_relevant_pc(caller_pc))
++ __sanitizer_weak_hook_strstr(caller_pc, s1, s2, result);
+ return result;
+ }
+
+ ATTRIBUTE_INTERFACE char *strcasestr(const char *s1, const char *s2) {
+ ensureFuzzerInited();
+ char *result = REAL(strcasestr)(s1, s2);
+- __sanitizer_weak_hook_strcasestr(GET_CALLER_PC(), s1, s2, result);
++ void *caller_pc = GET_CALLER_PC();
++ if (__sanitizer_weak_is_relevant_pc(caller_pc))
++ __sanitizer_weak_hook_strcasestr(caller_pc, s1, s2, result);
+ return result;
+ }
+
+@@ -212,7 +233,9 @@ ATTRIBUTE_INTERFACE
+ void *memmem(const void *s1, size_t len1, const void *s2, size_t len2) {
+ ensureFuzzerInited();
+ void *result = REAL(memmem)(s1, len1, s2, len2);
+- __sanitizer_weak_hook_memmem(GET_CALLER_PC(), s1, len1, s2, len2, result);
++ void *caller_pc = GET_CALLER_PC();
++ if (__sanitizer_weak_is_relevant_pc(caller_pc))
++ __sanitizer_weak_hook_memmem(caller_pc, s1, len1, s2, len2, result);
+ return result;
+ }
+
diff --git a/third_party/libFuzzer-pass-death-callback-to-jazzer.patch b/third_party/libFuzzer-pass-death-callback-to-jazzer.patch
new file mode 100644
index 00000000..3fb9fbb0
--- /dev/null
+++ b/third_party/libFuzzer-pass-death-callback-to-jazzer.patch
@@ -0,0 +1,28 @@
+diff --git compiler-rt/lib/fuzzer/FuzzerExtFunctions.def compiler-rt/lib/fuzzer/FuzzerExtFunctions.def
+index 51edf8444e94..e31f0040268b 100644
+--- compiler-rt/lib/fuzzer/FuzzerExtFunctions.def
++++ compiler-rt/lib/fuzzer/FuzzerExtFunctions.def
+@@ -42,7 +42,7 @@ EXT_FUNC(__sanitizer_symbolize_pc, void,
+ EXT_FUNC(__sanitizer_get_module_and_offset_for_pc, int,
+ (void *pc, char *module_path,
+ size_t module_path_len,void **pc_offset), false);
+-EXT_FUNC(__sanitizer_set_death_callback, void, (void (*)(void)), true);
++EXT_FUNC(__jazzer_set_death_callback, void, (void (*)(void)), true);
+ EXT_FUNC(__sanitizer_set_report_fd, void, (void*), false);
+ EXT_FUNC(__msan_scoped_disable_interceptor_checks, void, (), false);
+ EXT_FUNC(__msan_scoped_enable_interceptor_checks, void, (), false);
+diff --git compiler-rt/lib/fuzzer/FuzzerLoop.cpp compiler-rt/lib/fuzzer/FuzzerLoop.cpp
+index 149742b4c2fe..7b361423cc32 100644
+--- compiler-rt/lib/fuzzer/FuzzerLoop.cpp
++++ compiler-rt/lib/fuzzer/FuzzerLoop.cpp
+@@ -138,8 +138,8 @@ void Fuzzer::HandleMalloc(size_t Size) {
+ Fuzzer::Fuzzer(UserCallback CB, InputCorpus &Corpus, MutationDispatcher &MD,
+ FuzzingOptions Options)
+ : CB(CB), Corpus(Corpus), MD(MD), Options(Options) {
+- if (EF->__sanitizer_set_death_callback)
+- EF->__sanitizer_set_death_callback(StaticDeathCallback);
++ if (EF->__jazzer_set_death_callback)
++ EF->__jazzer_set_death_callback(StaticDeathCallback);
+ assert(!F);
+ F = this;
+ TPC.ResetMaps();
diff --git a/third_party/libFuzzer.BUILD b/third_party/libFuzzer.BUILD
new file mode 100644
index 00000000..4bd464a4
--- /dev/null
+++ b/third_party/libFuzzer.BUILD
@@ -0,0 +1,21 @@
+# Based on https://github.com/llvm/llvm-project/blob/llvmorg-11.1.0/compiler-rt/lib/fuzzer/build.sh
+LIB_FUZZER_PATH = "compiler-rt/lib/fuzzer"
+
+cc_library(
+ name = "libFuzzer",
+ srcs = glob([
+ LIB_FUZZER_PATH + "/*.cpp",
+ ]),
+ hdrs = glob([
+ LIB_FUZZER_PATH + "/*.h",
+ LIB_FUZZER_PATH + "/*.def",
+ ]),
+ copts = [
+ "-g",
+ "-O2",
+ "-fno-omit-frame-pointer",
+ "-std=c++11",
+ ],
+ alwayslink = True,
+ visibility = ["//visibility:public"],
+)